📖
Шаги, необходимые для решения поставленных задач:
!pip install -U scikit-learn -q
pip install phik
Requirement already satisfied: phik in c:\users\elena\anaconda3\lib\site-packages (0.12.4) Requirement already satisfied: numpy>=1.18.0 in c:\users\elena\anaconda3\lib\site-packages (from phik) (1.24.3) Requirement already satisfied: scipy>=1.5.2 in c:\users\elena\anaconda3\lib\site-packages (from phik) (1.10.1) Requirement already satisfied: pandas>=0.25.1 in c:\users\elena\anaconda3\lib\site-packages (from phik) (2.2.0) Requirement already satisfied: matplotlib>=2.2.3 in c:\users\elena\anaconda3\lib\site-packages (from phik) (3.7.1) Requirement already satisfied: joblib>=0.14.1 in c:\users\elena\anaconda3\lib\site-packages (from phik) (1.2.0) Requirement already satisfied: contourpy>=1.0.1 in c:\users\elena\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (1.0.5) Requirement already satisfied: cycler>=0.10 in c:\users\elena\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (0.11.0) Requirement already satisfied: fonttools>=4.22.0 in c:\users\elena\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (4.25.0) Requirement already satisfied: kiwisolver>=1.0.1 in c:\users\elena\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (1.4.4) Requirement already satisfied: packaging>=20.0 in c:\users\elena\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (23.0) Requirement already satisfied: pillow>=6.2.0 in c:\users\elena\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (9.4.0) Requirement already satisfied: pyparsing>=2.3.1 in c:\users\elena\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (3.0.9) Requirement already satisfied: python-dateutil>=2.7 in c:\users\elena\anaconda3\lib\site-packages (from matplotlib>=2.2.3->phik) (2.8.2) Requirement already satisfied: pytz>=2020.1 in c:\users\elena\anaconda3\lib\site-packages (from pandas>=0.25.1->phik) (2022.7) Requirement already satisfied: tzdata>=2022.7 in c:\users\elena\anaconda3\lib\site-packages (from pandas>=0.25.1->phik) (2024.1) Requirement already satisfied: six>=1.5 in c:\users\elena\anaconda3\lib\site-packages (from python-dateutil>=2.7->matplotlib>=2.2.3->phik) (1.16.0) Note: you may need to restart the kernel to use updated packages.
!pip install shap
Requirement already satisfied: shap in c:\users\elena\anaconda3\lib\site-packages (0.45.0) Requirement already satisfied: numpy in c:\users\elena\anaconda3\lib\site-packages (from shap) (1.24.3) Requirement already satisfied: scipy in c:\users\elena\anaconda3\lib\site-packages (from shap) (1.10.1) Requirement already satisfied: scikit-learn in c:\users\elena\anaconda3\lib\site-packages (from shap) (1.5.1) Requirement already satisfied: pandas in c:\users\elena\anaconda3\lib\site-packages (from shap) (2.2.0) Requirement already satisfied: tqdm>=4.27.0 in c:\users\elena\anaconda3\lib\site-packages (from shap) (4.65.0) Requirement already satisfied: packaging>20.9 in c:\users\elena\anaconda3\lib\site-packages (from shap) (23.0) Requirement already satisfied: slicer==0.0.7 in c:\users\elena\anaconda3\lib\site-packages (from shap) (0.0.7) Requirement already satisfied: numba in c:\users\elena\anaconda3\lib\site-packages (from shap) (0.57.0) Requirement already satisfied: cloudpickle in c:\users\elena\anaconda3\lib\site-packages (from shap) (2.2.1) Requirement already satisfied: colorama in c:\users\elena\anaconda3\lib\site-packages (from tqdm>=4.27.0->shap) (0.4.6) Requirement already satisfied: llvmlite<0.41,>=0.40.0dev0 in c:\users\elena\anaconda3\lib\site-packages (from numba->shap) (0.40.0) Requirement already satisfied: python-dateutil>=2.8.2 in c:\users\elena\anaconda3\lib\site-packages (from pandas->shap) (2.8.2) Requirement already satisfied: pytz>=2020.1 in c:\users\elena\anaconda3\lib\site-packages (from pandas->shap) (2022.7) Requirement already satisfied: tzdata>=2022.7 in c:\users\elena\anaconda3\lib\site-packages (from pandas->shap) (2024.1) Requirement already satisfied: joblib>=1.2.0 in c:\users\elena\anaconda3\lib\site-packages (from scikit-learn->shap) (1.2.0) Requirement already satisfied: threadpoolctl>=3.1.0 in c:\users\elena\anaconda3\lib\site-packages (from scikit-learn->shap) (3.5.0) Requirement already satisfied: six>=1.5 in c:\users\elena\anaconda3\lib\site-packages (from python-dateutil>=2.8.2->pandas->shap) (1.16.0)
# Импорт из стандартной библиотеки
import os
import warnings
import copy
warnings.filterwarnings("ignore")
# Стандартные библиотеки для научных вычислений
import numpy as np
import pandas as pd
from pandas import Index
from scipy.stats import ttest_ind
from scipy import stats as st
#import pandas_profiling
import ydata_profiling
from ydata_profiling import ProfileReport
# Визуализация данных
from wordcloud import WordCloud
import matplotlib as plt
import matplotlib.pyplot as plt
import matplotlib.pyplot as graph
import plotly.graph_objects as go
import plotly.express as px
import seaborn as sns
import sys
import shap
#импорт для работы с API
from google.cloud import translate_v2 as translate
import pycountry_convert
from pycountry_convert import country_alpha2_to_continent_code, country_name_to_country_alpha2
from fuzzywuzzy import fuzz
from fuzzywuzzy import process
# Расчет статистик
np.set_printoptions(threshold=np.inf)
from sklearn.metrics import make_scorer, roc_auc_score,mean_absolute_error, r2_score,accuracy_score,recall_score,precision_score,f1_score
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.model_selection import (
StratifiedKFold,
RandomizedSearchCV,
GridSearchCV,
KFold
)
from sklearn.pipeline import Pipeline, make_pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import (
OneHotEncoder,
LabelEncoder,
OrdinalEncoder,
StandardScaler,
MinMaxScaler,
RobustScaler,
)
# Модели машинного обучения
import sklearn.metrics as metrics
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve, auc
from sklearn.dummy import DummyClassifier, DummyRegressor
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.neighbors import KNeighborsRegressor, KNeighborsClassifier
from sklearn.tree import DecisionTreeRegressor, DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import RandomizedSearchCV
from sklearn.compose import ColumnTransformer
from sklearn.inspection import permutation_importance
from sklearn.impute import SimpleImputer
from sklearn.feature_selection import SelectKBest
from sklearn.preprocessing import LabelEncoder
from plotly.offline import init_notebook_mode, iplot, plot
import plotly.express as px
from sklearn.experimental import enable_halving_search_cv
from sklearn.model_selection import (cross_validate, StratifiedKFold, train_test_split,GridSearchCV,
HalvingGridSearchCV)
# Дополнительные библиотеки для анализа данных
from phik import phik_matrix
from phik import report
from phik.report import plot_correlation_matrix
from sklearn import preprocessing
from sklearn import utils
from sklearn import ensemble
import math
# Настройка отображения графиков
%matplotlib inline
sns.set(
font_scale=1.3,
style="whitegrid",
rc={'figure.figsize':(20,7)}
)
shap.initjs()
# Установка опции отображения широких датафреймов
pd.set_option('display.max_colwidth', None)
pd.set_option('display.float_format', '{:.2f}'.format)
# Константы
line_1 = '-'*125
line_2 = '_'*125
RANDOM_STATE = 42
TEST_SIZE = 0.25
NSAMPLES = 3
try:
df_train = pd.read_csv('/datasets/train_job_satisfaction_rate.csv', delimiter = ",",decimal=".")
df_test = pd.read_csv('/datasets/test_features.csv', delimiter = ",", decimal=".")
df_test_target = pd.read_csv('/datasets/test_target_job_satisfaction_rate.csv', delimiter = ",", decimal=".")
except:
df_train = pd.read_csv('https://code.s3.yandex.net/datasets/train_job_satisfaction_rate.csv', delimiter = ",", decimal=".")
df_test = pd.read_csv('https://code.s3.yandex.net/datasets/test_features.csv', delimiter = ",", decimal=".")
df_test_target = pd.read_csv('https://code.s3.yandex.net/datasets/test_target_job_satisfaction_rate.csv', delimiter = ",", decimal=".")
target_col = 'job_satisfaction_rate'
Необходимые функции:
def optimize_memory_usage(df: pd.DataFrame, print_size: bool=True) -> pd.DataFrame:
'''
Function optimizes memory usage in dataframe
df: pd.DataFrame - data table
print_size: bool - display of optimization results
return pd.DataFrame - amount of optimized memory
'''
numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64'] # Типы, которые будем проверять на оптимизацию
# Размер занимаемой памяти до оптимизации (в Мб)
before_size = df.memory_usage().sum() / 1024**2
for column in df.columns:
column_type = df[column].dtypes
if column_type in numerics:
column_min = df[column].min()
column_max = df[column].max()
if str(column_type).startswith('int'):
if column_min > np.iinfo(np.int8).min and column_max < np.iinfo(np.int8).max:
df[column] = df[column].astype(np.int8)
elif column_min > np.iinfo(np.int16).min and column_max < np.iinfo(np.int16).max:
df[column] = df[column].astype(np.int16)
elif column_min > np.iinfo(np.int32).min and column_max < np.iinfo(np.int32).max:
df[column] = df[column].astype(np.int32)
elif column_min > np.iinfo(np.int64).min and column_max < np.iinfo(np.int64).max:
df[column] = df[column].astype(np.int64)
else:
if column_min > np.finfo(np.float32).min and column_max < np.finfo(np.float32).max:
df[column] = df[column].astype(np.float32)
else:
df[column] = df[column].astype(np.float64)
# Размер занимаемой памяти после оптимизации (в Мб)
after_size = df.memory_usage().sum() / 1024**2
if print_size: print('Размер использования памяти: до {:5.2f} Mb - после {:5.2f} Mb ({:.1f}%)'
.format(before_size, after_size, 100 * (before_size - after_size) / before_size))
return df
def rename(df):
df.columns = df.columns.str.replace('\[.*?\]', '', regex=True)
df.columns = df.columns.str.strip()
# df.columns = df.columns.str.replace(' ', '')
return df
def set_frame_style(df, caption=""):
"""Helper function to set dataframe presentation style.
"""
return df.style.background_gradient(cmap='Blues').set_caption(caption).set_table_styles([{
'selector': 'caption',
'props': [
('color', 'Blue'),
('font-size', '18px'),
('font-weight','bold')
]}])
def check_data(data, title):
cols = data.columns.to_list()
display(set_frame_style(data[cols].head(),f'{title}: Первые 5 строк датафрейма'))
display(set_frame_style(data[cols].describe(),f'{title}: Статистики'))
display(set_frame_style(data[cols].nunique().to_frame().rename({0:'Уникальные значения'}, axis=1).transpose(), f'{title}: Unique Value Counts In Each Column'))
display(set_frame_style(data[cols].isna().sum().to_frame().transpose(), f'{title}:Столбцы с пропусками'))
def plot_feature(df, feature, figsize=(16,5), palette='colorblind', name='Train'):
df = df.fillna('Nan')
fig, ax = plt.subplots(1, 2, figsize=figsize)
ax = ax.flatten()
# Pie chart
pie_colors = sns.color_palette(palette, len(df[ feature].unique()))
ax[0].pie(
df[feature].value_counts(),
shadow=True,
#explode==[0.05] * len(df[feature].unique()),
autopct='%1.f%%',
textprops={'size': 15, 'color': 'white'},
colors=pie_colors
)
ax[0].set_aspect('equal') # Fix the aspect ratio to make the pie chart circular
# Bar plot
bar_colors = sns.color_palette(palette)
sns.countplot(
data=df,
y= feature,
ax=ax[1],
palette=bar_colors
)
ax[1].set_xlabel('Count', fontsize=14)
ax[1].set_ylabel('')
ax[1].tick_params(labelsize=12)
ax[1].yaxis.set_tick_params(width=0) # Remove tick lines for y-axis
fig.suptitle(f'{ feature} in {name} Dataset', fontsize=16, fontweight='bold')
plt.tight_layout()
# Show the plot
plt.show()
def plot_histogram_with_stats(df):
"""
Функция для построения гистограммы с основной статистикой для числовых столбцов датафрейма.
Параметры:
df (pandas.DataFrame) - датафрейм, для которого необходимо построить гистограмму.
"""
# Выбираем только числовые столбцы
numeric_cols = df.select_dtypes(include=['int32', 'int8', 'float32']).columns
if not numeric_cols.empty:
# Создаем фигуру и оси
n_cols = 2 # Количество колонок подграфиков
n_rows = (len(numeric_cols) + n_cols - 1) // n_cols # Количество строк подграфиков
fig, axes = plt.subplots(n_rows, n_cols, figsize=(15,5* n_rows))
# Проходим по каждому числовому столбцу
for i, col in enumerate(numeric_cols):
# Гистограмма
sns.histplot(data=df, x=df[col], bins=20, kde=True, ax=axes[i])
axes[i].set_title(f'Histogram of {col}')
# Средняя и медианная линии
mean = df[col].mean()
median = df[col].median()
axes[i].axvline(mean, color='r', linestyle='--', label='Mean')
axes[i].axvline(median, color='g', linestyle='--', label='Median')
axes[i].legend()
# Основная статистика
stats = df[col].describe()
axes[i].text(0.1, 0.5,f'Mean: {stats["mean"]:.2f}\nMedian: {stats["50%"]:.2f}\nStd: {stats["std"]:.2f}\nMin: {stats["min"]:.2f}\nMax: {stats["max"]:.2f}', transform=axes[i].transAxes)
for j in range(i + 1, len(axes)):
fig.delaxes(axes[j])
plt.tight_layout()
plt.show()
else:
print("Датафрейм не содержит числовых столбцов.")
Функция для подсчета метрики:
def calculate_smape(actual, predicted):
"""
Calculate the Symmetric Mean Absolute Percentage Error (SMAPE)
:param actual: list of actual values
:param predicted: list of predicted values
:return: SMAPE as a percentage
"""
# Ensure actual and predicted lists have the same length
if len(actual) != len(predicted):
raise ValueError("Input lists must have the same length")
# Calculate SMAPE
n = len(actual)
total_error = 0
for a, p in zip(actual, predicted):
total_error += 2 * abs(a - p) / (abs(a) + abs(p))
smape = (total_error / n) * 100
return smape
scorer_s = make_scorer(calculate_smape, greater_is_better=False)
display(df_train.head())
df_train.tail()
print()
df_train.sample()
df_train.info(memory_usage='deep')
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 155278 | sales | junior | medium | 2 | no | no | 1 | 24000 | 0.58 |
| 1 | 653870 | hr | junior | high | 2 | no | no | 5 | 38400 | 0.76 |
| 2 | 184592 | sales | junior | low | 1 | no | no | 2 | 12000 | 0.11 |
| 3 | 171431 | technology | junior | low | 4 | no | no | 2 | 18000 | 0.37 |
| 4 | 693419 | hr | junior | medium | 1 | no | no | 3 | 22800 | 0.20 |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 4000 entries, 0 to 3999 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 4000 non-null int64 1 dept 3994 non-null object 2 level 3996 non-null object 3 workload 4000 non-null object 4 employment_years 4000 non-null int64 5 last_year_promo 4000 non-null object 6 last_year_violations 4000 non-null object 7 supervisor_evaluation 4000 non-null int64 8 salary 4000 non-null int64 9 job_satisfaction_rate 4000 non-null float64 dtypes: float64(1), int64(4), object(5) memory usage: 1.3 MB
Тренировочная выборка: • id — уникальный идентификатор сотрудника; • dept — отдел, в котором работает сотрудник; • level — уровень занимаемой должности; • workload — уровень загруженности сотрудника; • employment_years — длительность работы в компании (в годах); • last_year_promo — показывает, было ли повышение за последний год; • last_year_violations — показывает, нарушал ли сотрудник трудовой договор за последний год; • supervisor_evaluation — оценка качества работы сотрудника, которую дал руководитель; • salary — ежемесячная зарплата сотрудника; • job_satisfaction_rate — уровень удовлетворённости сотрудника работой в компании, целевой признак.
Датафрейм ‘data_train_job_satisfaction_rate’ имеет 9 столбцов и 4000 строк, имеются пропуски в 2 столбцах. Вес датафрейма 1,3мб.Тип данных соответствует
display(df_test.head())
df_test.tail()
print()
df_test.sample()
df_test.info(memory_usage='deep')
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 |
| 1 | 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 |
| 2 | 467458 | sales | middle | low | 5 | no | no | 4 | 19200 |
| 3 | 418655 | sales | middle | low | 6 | no | no | 4 | 19200 |
| 4 | 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 2000 entries, 0 to 1999 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 2000 non-null int64 1 dept 1998 non-null object 2 level 1999 non-null object 3 workload 2000 non-null object 4 employment_years 2000 non-null int64 5 last_year_promo 2000 non-null object 6 last_year_violations 2000 non-null object 7 supervisor_evaluation 2000 non-null int64 8 salary 2000 non-null int64 dtypes: int64(4), object(5) memory usage: 662.0 KB
Входные признаки тестовой выборки
Датафрейм ‘data_test_features’ имеет 8 столбцов и 2000 строк, имеются пропуски в 2 столбцах. Вес датафрейма 662,0 кб. Тип данных соответствует
display(df_test_target.head())
df_test_target.tail()
print()
df_test_target.sample()
df_test_target.info(memory_usage='deep')
| id | job_satisfaction_rate | |
|---|---|---|
| 0 | 130604 | 0.74 |
| 1 | 825977 | 0.75 |
| 2 | 418490 | 0.60 |
| 3 | 555320 | 0.72 |
| 4 | 826430 | 0.08 |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 2000 entries, 0 to 1999 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 2000 non-null int64 1 job_satisfaction_rate 2000 non-null float64 dtypes: float64(1), int64(1) memory usage: 31.4 KB
Целевой признак тестовой выборки
Датафрейм ‘data_test_target_job_satisfaction_rate’ имеет 2 столбца и 2000 строк, без пропусков. Вес датафрейма 662,0 кб. Тип данных соответствует
optimize_memory_usage(df_train)
Размер использования памяти: до 0.31 Mb - после 0.21 Mb (32.5%)
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 155278 | sales | junior | medium | 2 | no | no | 1 | 24000 | 0.58 |
| 1 | 653870 | hr | junior | high | 2 | no | no | 5 | 38400 | 0.76 |
| 2 | 184592 | sales | junior | low | 1 | no | no | 2 | 12000 | 0.11 |
| 3 | 171431 | technology | junior | low | 4 | no | no | 2 | 18000 | 0.37 |
| 4 | 693419 | hr | junior | medium | 1 | no | no | 3 | 22800 | 0.20 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3995 | 457950 | technology | junior | high | 2 | no | no | 3 | 46800 | 0.45 |
| 3996 | 957499 | sales | junior | medium | 2 | no | no | 4 | 21600 | 0.68 |
| 3997 | 533318 | sales | middle | low | 7 | no | no | 5 | 24000 | 0.76 |
| 3998 | 706868 | sales | junior | medium | 2 | no | no | 3 | 24000 | 0.48 |
| 3999 | 338347 | technology | middle | medium | 5 | no | yes | 1 | 44400 | 0.18 |
4000 rows × 10 columns
optimize_memory_usage(df_test)
Размер использования памяти: до 0.14 Mb - после 0.10 Mb (30.5%)
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 |
| 1 | 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 |
| 2 | 467458 | sales | middle | low | 5 | no | no | 4 | 19200 |
| 3 | 418655 | sales | middle | low | 6 | no | no | 4 | 19200 |
| 4 | 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1995 | 393147 | marketing | junior | low | 3 | no | no | 3 | 20400 |
| 1996 | 109582 | technology | middle | low | 3 | no | no | 2 | 21600 |
| 1997 | 305653 | technology | junior | low | 1 | no | no | 3 | 14400 |
| 1998 | 160233 | technology | middle | low | 8 | no | no | 1 | 32400 |
| 1999 | 771859 | purchasing | middle | medium | 1 | no | no | 4 | 38400 |
2000 rows × 9 columns
optimize_memory_usage(df_test_target)
Размер использования памяти: до 0.03 Mb - после 0.02 Mb (49.8%)
| id | job_satisfaction_rate | |
|---|---|---|
| 0 | 130604 | 0.74 |
| 1 | 825977 | 0.75 |
| 2 | 418490 | 0.60 |
| 3 | 555320 | 0.72 |
| 4 | 826430 | 0.08 |
| ... | ... | ... |
| 1995 | 405257 | 0.28 |
| 1996 | 370925 | 0.21 |
| 1997 | 857722 | 0.39 |
| 1998 | 303402 | 0.26 |
| 1999 | 648995 | 0.62 |
2000 rows × 2 columns
С помощью функции оптимизировали размеры датафреймов на 32,5, 30, 30,5%
Разделим столбцы по типам данных
num_cols = [col for col in df_train.columns if df_train[col].dtype != 'object']
num_cols
['id', 'employment_years', 'supervisor_evaluation', 'salary', 'job_satisfaction_rate']
cat_cols = ['dept', 'level', 'workload']
bin_cols = ['last_year_promo', 'last_year_violations']
for col in cat_cols:
print(f"{col} has {df_train[col].unique()} values\n")
dept has ['sales' 'hr' 'technology' 'purchasing' 'marketing' nan] values level has ['junior' 'middle' 'sinior' nan] values workload has ['medium' 'high' 'low'] values
for col in cat_cols:
print(f"{col} has {df_test[col].unique()} values\n")
dept has ['marketing' 'hr' 'sales' 'purchasing' 'technology' nan ' '] values level has ['junior' 'middle' 'sinior' nan] values workload has ['medium' 'low' 'high' ' '] values
for col in bin_cols:
print(f"{col} has {df_train[col].unique()} values\n")
last_year_promo has ['no' 'yes'] values last_year_violations has ['no' 'yes'] values
for col in bin_cols:
print(f"{col} has {df_test[col].unique()} values\n")
last_year_promo has ['no' 'yes'] values last_year_violations has ['no' 'yes'] values
Обработаем аномалии
df_test['dept'] = df_test['dept'].replace(' ', np.nan)
df_test['workload'] = df_test['workload'].replace(' ', np.nan)
Выведем статистику данных
target_col = 'job_satisfaction_rate'
num_cols = [
'salary'
]
cat_1_cols = [
'employment_years',
'supervisor_evaluation',
]
cat_cols = [
'dept',
'level',
'workload',
'last_year_promo',
'last_year_violations'
]
check_data(df_train, 'Train data')
print('-'*100)
check_data(df_test, 'Test data')
print('-'*100)
check_data(df_test_target, 'df_test_target');
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 155278 | sales | junior | medium | 2 | no | no | 1 | 24000 | 0.580000 |
| 1 | 653870 | hr | junior | high | 2 | no | no | 5 | 38400 | 0.760000 |
| 2 | 184592 | sales | junior | low | 1 | no | no | 2 | 12000 | 0.110000 |
| 3 | 171431 | technology | junior | low | 4 | no | no | 2 | 18000 | 0.370000 |
| 4 | 693419 | hr | junior | medium | 1 | no | no | 3 | 22800 | 0.200000 |
| id | employment_years | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|
| count | 4000.000000 | 4000.000000 | 4000.000000 | 4000.000000 | 4000.000000 |
| mean | 544957.621000 | 3.718500 | 3.476500 | 33926.700000 | 0.533995 |
| std | 257883.104622 | 2.542513 | 1.008812 | 14900.703838 | 0.225327 |
| min | 100954.000000 | 1.000000 | 1.000000 | 12000.000000 | 0.030000 |
| 25% | 322836.750000 | 2.000000 | 3.000000 | 22800.000000 | 0.360000 |
| 50% | 534082.500000 | 3.000000 | 4.000000 | 30000.000000 | 0.560000 |
| 75% | 771446.000000 | 6.000000 | 4.000000 | 43200.000000 | 0.710000 |
| max | 999521.000000 | 10.000000 | 5.000000 | 98400.000000 | 1.000000 |
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| Уникальные значения | 4000 | 5 | 3 | 3 | 10 | 2 | 2 | 5 | 68 | 98 |
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 6 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
----------------------------------------------------------------------------------------------------
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 |
| 1 | 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 |
| 2 | 467458 | sales | middle | low | 5 | no | no | 4 | 19200 |
| 3 | 418655 | sales | middle | low | 6 | no | no | 4 | 19200 |
| 4 | 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 |
| id | employment_years | supervisor_evaluation | salary | |
|---|---|---|---|---|
| count | 2000.000000 | 2000.000000 | 2000.000000 | 2000.000000 |
| mean | 552765.213500 | 3.666500 | 3.526500 | 34066.800000 |
| std | 253851.326129 | 2.537222 | 0.996892 | 15398.436729 |
| min | 100298.000000 | 1.000000 | 1.000000 | 12000.000000 |
| 25% | 339052.000000 | 1.000000 | 3.000000 | 22800.000000 |
| 50% | 550793.000000 | 3.000000 | 4.000000 | 30000.000000 |
| 75% | 765763.750000 | 6.000000 | 4.000000 | 43200.000000 |
| max | 999029.000000 | 10.000000 | 5.000000 | 96000.000000 |
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| Уникальные значения | 2000 | 5 | 3 | 3 | 10 | 2 | 2 | 5 | 66 |
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 3 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
----------------------------------------------------------------------------------------------------
| id | job_satisfaction_rate | |
|---|---|---|
| 0 | 130604 | 0.740000 |
| 1 | 825977 | 0.750000 |
| 2 | 418490 | 0.600000 |
| 3 | 555320 | 0.720000 |
| 4 | 826430 | 0.080000 |
| id | job_satisfaction_rate | |
|---|---|---|
| count | 2000.000000 | 2000.000000 |
| mean | 552765.213500 | 0.548780 |
| std | 253851.326129 | 0.220110 |
| min | 100298.000000 | 0.030000 |
| 25% | 339052.000000 | 0.380000 |
| 50% | 550793.000000 | 0.580000 |
| 75% | 765763.750000 | 0.720000 |
| max | 999029.000000 | 1.000000 |
| id | job_satisfaction_rate | |
|---|---|---|
| Уникальные значения | 2000 | 98 |
| id | job_satisfaction_rate | |
|---|---|---|
| 0 | 0 | 0 |
Согласно полученным данным, в df_train и df_test присутствует 10 и 3 пропущенных значений. В столбце dept DataFrame df_train содержится 6 пропущенных значений. Для обработки этих пропусков в пайплайне предлагается использовать метод SimpleImputer с параметром strategy='most_frequent', либо IterativeImputer для более точного заполнения. В столбце level DataFrame df_train содержится 4 пропущенных значения. В столбце dept DataFrame df_test содержится 2 пропущенных значения. Явных дубликатов не найдено.
В тренировочной выборке средний стаж работы составляет 2,5, максимальный 10 лет, наблюдается средняя оценка супервайзера чуть больше 1, максимальная зарплата 98400, средняя 14900, средняя удовлетворенность от работы 20%
В тестовой выборке средний стаж работы составляет 2,5, максимальный 10 лет, наблюдается средняя оценка супервайзера 1, максимальная зарплата 96000, средняя 15398, средняя удовлетворенность от работы 22%
rename(df_train)
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 155278 | sales | junior | medium | 2 | no | no | 1 | 24000 | 0.58 |
| 1 | 653870 | hr | junior | high | 2 | no | no | 5 | 38400 | 0.76 |
| 2 | 184592 | sales | junior | low | 1 | no | no | 2 | 12000 | 0.11 |
| 3 | 171431 | technology | junior | low | 4 | no | no | 2 | 18000 | 0.37 |
| 4 | 693419 | hr | junior | medium | 1 | no | no | 3 | 22800 | 0.20 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3995 | 457950 | technology | junior | high | 2 | no | no | 3 | 46800 | 0.45 |
| 3996 | 957499 | sales | junior | medium | 2 | no | no | 4 | 21600 | 0.68 |
| 3997 | 533318 | sales | middle | low | 7 | no | no | 5 | 24000 | 0.76 |
| 3998 | 706868 | sales | junior | medium | 2 | no | no | 3 | 24000 | 0.48 |
| 3999 | 338347 | technology | middle | medium | 5 | no | yes | 1 | 44400 | 0.18 |
4000 rows × 10 columns
rename(df_test)
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 |
| 1 | 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 |
| 2 | 467458 | sales | middle | low | 5 | no | no | 4 | 19200 |
| 3 | 418655 | sales | middle | low | 6 | no | no | 4 | 19200 |
| 4 | 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1995 | 393147 | marketing | junior | low | 3 | no | no | 3 | 20400 |
| 1996 | 109582 | technology | middle | low | 3 | no | no | 2 | 21600 |
| 1997 | 305653 | technology | junior | low | 1 | no | no | 3 | 14400 |
| 1998 | 160233 | technology | middle | low | 8 | no | no | 1 | 32400 |
| 1999 | 771859 | purchasing | middle | medium | 1 | no | no | 4 | 38400 |
2000 rows × 9 columns
rename(df_test_target)
| id | job_satisfaction_rate | |
|---|---|---|
| 0 | 130604 | 0.74 |
| 1 | 825977 | 0.75 |
| 2 | 418490 | 0.60 |
| 3 | 555320 | 0.72 |
| 4 | 826430 | 0.08 |
| ... | ... | ... |
| 1995 | 405257 | 0.28 |
| 1996 | 370925 | 0.21 |
| 1997 | 857722 | 0.39 |
| 1998 | 303402 | 0.26 |
| 1999 | 648995 | 0.62 |
2000 rows × 2 columns
Вывод: После проведения анализа наличия дубликатов и ошибок в категориальных данных мы получили следующие результаты:
Дубликаты: В данных отсутствуют явные дубликаты. Это подтверждается отсутствием повторяющихся строк во всех рассматриваемых датафреймах.
Уникальные значения: Проведен анализ уникальных значений для каждого категориального столбца в каждом датафрейме. Обнаружены некоторые особенности:
В столбцах dept и workload датафрейма df_test обнаружены значения, заполненные пробелами. Эти пропуски могли возникнуть из-за нежелания сотрудников предоставлять информацию или ошибок при заполнении данных. Рекомендуется использовать метод SimpleImputer с параметром strategy='constant' для заполнения NaN значений. Исходя из результатов анализа, данные готовы к дальнейшей обработке и построению модели. В следующих разделах будут предприняты дальнейшие шаги по предобработке и анализу данных, а также построению моделей.
После анализа количественных данных было обнаружено следующее:
Во всех рассматриваемых датафреймах (df_train, df_test, df_test_target) не было найдено аномальных значений или ошибок в количественных столбцах (employment_years, supervisor_evaluation, salary, job_satisfaction_rate). Таким образом, данные готовы к дальнейшему анализу и использованию в моделировании.
Посмотрим как распределены признаки в тренировочной и тестовой выборках
plot_histogram_with_stats(df_train.drop(['id','employment_years', 'supervisor_evaluation'], axis=1))
# Рассчитываем частоты в процентах
value_counts_normalized = df_train['employment_years'].value_counts(normalize=True).reset_index()
value_counts_normalized.columns = ['employment_years', 'frequency']
# Умножаем на 100, чтобы преобразовать доли в проценты
value_counts_normalized['frequency'] *= 100
plt.figure(figsize=(15, 6))
# Построение графика абсолютных величин в процентах
sns.barplot(x='employment_years', y='frequency', data=value_counts_normalized)
plt.title('Распределение employment_years в процентах')
plt.xlabel('Годы занятости')
plt.ylabel('Процент')
plt.show()
# Рассчитываем частоты в процентах
value_counts_normalized = df_train['supervisor_evaluation'].value_counts(normalize=True).reset_index()
value_counts_normalized.columns = ['supervisor_evaluation', 'frequency']
# Умножаем на 100, чтобы преобразовать доли в проценты
value_counts_normalized['frequency'] *= 100
plt.figure(figsize=(15, 6))
# Построение графика абсолютных величин в процентах
sns.barplot(x='supervisor_evaluation', y='frequency', data=value_counts_normalized)
plt.title('Распределение supervisor_evaluation в процентах')
plt.xlabel('Оценка руководителя')
plt.ylabel('Процент')
plt.show()
plot_histogram_with_stats(df_test.drop(['id','employment_years', 'supervisor_evaluation'], axis=1))
# Рассчитываем частоты в процентах
value_counts_normalized = df_test['employment_years'].value_counts(normalize=True).reset_index()
value_counts_normalized.columns = ['employment_years', 'frequency']
# Умножаем на 100, чтобы преобразовать доли в проценты
value_counts_normalized['frequency'] *= 100
plt.figure(figsize=(15, 6))
# Построение графика абсолютных величин в процентах
sns.barplot(x='employment_years', y='frequency', data=value_counts_normalized)
plt.title('Распределение employment_years в процентах')
plt.xlabel('Годы занятости')
plt.ylabel('Процент')
plt.show()
# Рассчитываем частоты в процентах
value_counts_normalized = df_test['supervisor_evaluation'].value_counts(normalize=True).reset_index()
value_counts_normalized.columns = ['supervisor_evaluation', 'frequency']
# Умножаем на 100, чтобы преобразовать доли в проценты
value_counts_normalized['frequency'] *= 100
plt.figure(figsize=(15, 6))
# Построение графика абсолютных величин в процентах
sns.barplot(x='supervisor_evaluation', y='frequency', data=value_counts_normalized)
plt.title('Распределение supervisor_evaluation в процентах')
plt.xlabel('Оценка руководителя')
plt.ylabel('Процент')
plt.show()
В тренировочной выборке больше всего работников со стаже от 1,5 до 2,5 лет, чаще всего имеют оценку руководителя 4 зарплату 1800-4000. Распределение нормальное, наблюдается правый хвост.
В тестовой выборке размер заработной платы аналогичен тренировочным данным. Также имеем правый хвост в распределении, с повышением заработной платы, количество сторудников уменьшается
В тестовой выборке большое количество сотрудников с минимальным стажем, и оценкой руководителя 3-4.
corr_matrix_df = df_train.drop(['id'], axis=1).phik_matrix(interval_cols=['employment_years', 'supervisor_evaluation', 'salary',
'job_satisfaction_rate'])
corr_matrix_df.round(2)
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|
| dept | 1.00 | 0.00 | 0.02 | 0.04 | 0.00 | 0.00 | 0.11 | 0.28 | 0.08 |
| level | 0.00 | 1.00 | 0.42 | 0.68 | 0.10 | 0.00 | 0.00 | 0.72 | 0.08 |
| workload | 0.02 | 0.42 | 1.00 | 0.18 | 0.06 | 0.00 | 0.04 | 0.79 | 0.06 |
| employment_years | 0.04 | 0.68 | 0.18 | 1.00 | 0.18 | 0.06 | 0.00 | 0.48 | 0.33 |
| last_year_promo | 0.00 | 0.10 | 0.06 | 0.18 | 1.00 | 0.00 | 0.01 | 0.22 | 0.19 |
| last_year_violations | 0.00 | 0.00 | 0.00 | 0.06 | 0.00 | 1.00 | 0.18 | 0.01 | 0.56 |
| supervisor_evaluation | 0.11 | 0.00 | 0.04 | 0.00 | 0.01 | 0.18 | 1.00 | 0.00 | 0.76 |
| salary | 0.28 | 0.72 | 0.79 | 0.48 | 0.22 | 0.01 | 0.00 | 1.00 | 0.17 |
| job_satisfaction_rate | 0.08 | 0.08 | 0.06 | 0.33 | 0.19 | 0.56 | 0.76 | 0.17 | 1.00 |
plot_correlation_matrix(
corr_matrix_df.values,
x_labels=corr_matrix_df.columns,
y_labels=corr_matrix_df.index,
vmin=0, vmax=1, color_map='PuOr',
title=r'Корреляция $\phi_K$',
fontsize_factor=1.5,
figsize=(20, 15)
)
corr_matrix_df_test = df_test.drop(['id'], axis=1).phik_matrix(interval_cols=['employment_years', 'supervisor_evaluation', 'salary',
'job_satisfaction_rate'])
corr_matrix_df_test.round(2)
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|
| dept | 1.00 | 0.00 | 0.03 | 0.00 | 0.00 | 0.00 | 0.06 | 0.33 |
| level | 0.00 | 1.00 | 0.47 | 0.69 | 0.12 | 0.02 | 0.00 | 0.75 |
| workload | 0.03 | 0.47 | 1.00 | 0.20 | 0.04 | 0.00 | 0.04 | 0.79 |
| employment_years | 0.00 | 0.69 | 0.20 | 1.00 | 0.17 | 0.07 | 0.00 | 0.49 |
| last_year_promo | 0.00 | 0.12 | 0.04 | 0.17 | 1.00 | 0.00 | 0.01 | 0.19 |
| last_year_violations | 0.00 | 0.02 | 0.00 | 0.07 | 0.00 | 1.00 | 0.19 | 0.00 |
| supervisor_evaluation | 0.06 | 0.00 | 0.04 | 0.00 | 0.01 | 0.19 | 1.00 | 0.11 |
| salary | 0.33 | 0.75 | 0.79 | 0.49 | 0.19 | 0.00 | 0.11 | 1.00 |
plot_correlation_matrix(
corr_matrix_df_test.values,
x_labels=corr_matrix_df_test.columns,
y_labels=corr_matrix_df_test.index,
vmin=0, vmax=1, color_map='PuOr',
title=r'Корреляция $\phi_K$',
fontsize_factor=1.5,
figsize=(20, 15)
)
Наибольшая связь наблюдается между признаками уровень заработной платы и employment_years.
d = df_train[['employment_years','supervisor_evaluation','salary','job_satisfaction_rate']]
sns.pairplot(d, diag_kind = 'kde', hue = 'job_satisfaction_rate', plot_kws={'alpha':0.5, 'edgecolor': 'k'})
<seaborn.axisgrid.PairGrid at 0x1f564131190>
Посмотрим как распределяются категориальные признаки в тренировочной и тестовой выборках.
plot_feature(df_train, 'dept', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_test, 'dept', figsize=(16,5), palette='colorblind', name='Test data')
plot_feature(df_train, 'level', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_test, 'level', figsize=(16,5), palette='colorblind', name='Test data')
plot_feature(df_train, 'workload', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_test, 'workload', figsize=(16,5), palette='colorblind', name='Test data')
plot_feature(df_train, 'last_year_promo', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_test, 'last_year_promo', figsize=(16,5), palette='colorblind', name='Test data')
plot_feature(df_train, 'last_year_violations', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_test, 'last_year_violations', figsize=(16,5), palette='colorblind', name='Test data')
На основании визуализаций видно, что большинство сотрудников работает в отделе продаж, уровень джуниор, сильно загружены и не получали взысканий и повышений за последний год.И в тренировочной и в тестовой выборках, признаки распределены равномерно.
Описание количественных параметров: Гистограммы и статистический анализ выявили распределение ключевых количественных переменных, таких как стаж работы, оценки руководства, уровень зарплаты и уровень удовлетворенности работой. Обнаружены различия в максимальной зарплате и длительности работы в компании между различными наборами данных.
Круговые диаграммы показали распределение сотрудников по отделам, уровням карьеры, рабочей нагрузке, наличию повышений и дисциплинарных нарушений.
Визуализация данных показала, что удовлетворенность работой, уровень зарплаты и оценки руководителей зависят от категориальных переменных, таких как отдел, уровень карьеры и рабочая нагрузка.
Подготовка данных в пайплайн.
df_train.columns = df_train.columns.str.strip().str.lower().str.replace(' ', '_').str.replace('(', '').str.replace(')', '')
df_train.head()
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 155278 | sales | junior | medium | 2 | no | no | 1 | 24000 | 0.58 |
| 1 | 653870 | hr | junior | high | 2 | no | no | 5 | 38400 | 0.76 |
| 2 | 184592 | sales | junior | low | 1 | no | no | 2 | 12000 | 0.11 |
| 3 | 171431 | technology | junior | low | 4 | no | no | 2 | 18000 | 0.37 |
| 4 | 693419 | hr | junior | medium | 1 | no | no | 3 | 22800 | 0.20 |
df_test.columns = df_test.columns.str.strip().str.lower().str.replace(' ', '_').str.replace('(', '').str.replace(')', '')
df_test.head()
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 |
| 1 | 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 |
| 2 | 467458 | sales | middle | low | 5 | no | no | 4 | 19200 |
| 3 | 418655 | sales | middle | low | 6 | no | no | 4 | 19200 |
| 4 | 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 |
df_train[
df_train.select_dtypes(["object"]).columns
] = df_train.select_dtypes(["object"]).apply(lambda x: x.astype("category"))
df_test[
df_test.select_dtypes(["object"]).columns
] = df_test.select_dtypes(["object"]).apply(lambda x: x.astype("category"))
df_train = df_train.set_index('id')
duplicateRows_train = df_train[df_train.duplicated ()]
df_train = df_train.drop_duplicates (keep= False )
df_test = (df_test.merge(df_test_target, on="id").reset_index(drop=True))
df_test = df_test.set_index('id')
RANDOM_STATE = 42
# загружаем данные
X_train = df_train.drop(columns=['job_satisfaction_rate'], axis=1)
y_train = df_train['job_satisfaction_rate']
X_test = df_test.drop(columns=['job_satisfaction_rate'], axis=1)
y_test = df_test['job_satisfaction_rate']
X_train.shape, X_test.shape
((3545, 8), (2000, 8))
y_train.shape,y_test.shape
((3545,), (2000,))
# создаём списки с названиями признаков
ohe_columns = ['last_year_promo', 'last_year_violations', 'dept']
ord_columns = ['level', 'workload']
num_columns = ['employment_years','salary', 'supervisor_evaluation']
# создаём пайплайн для подготовки признаков из списка ohe_columns: заполнение пропусков и OHE-кодирование
# SimpleImputer + OHE
ohe_pipe = Pipeline(
[
('simpleImputer_ohe', SimpleImputer(missing_values=np.nan, strategy='most_frequent')),
('ohe', OneHotEncoder(drop='first', handle_unknown='ignore', sparse_output=False))
]
)
print(ohe_pipe)
Pipeline(steps=[('simpleImputer_ohe', SimpleImputer(strategy='most_frequent')),
('ohe',
OneHotEncoder(drop='first', handle_unknown='ignore',
sparse_output=False))])
ord_pipe = Pipeline(
[('simpleImputer_before_ord', SimpleImputer(missing_values=np.nan, strategy='most_frequent')),
('ord', OrdinalEncoder(
categories=[
['junior', 'middle', 'sinior'],
['medium', 'low', 'high']
],
handle_unknown='use_encoded_value', unknown_value=np.nan
)
),
('simpleImputer_after_ord', SimpleImputer(missing_values=np.nan, strategy='most_frequent'))
]
)
# создаём общий пайплайн для подготовки данных
data_preprocessor = ColumnTransformer(
[
('ohe', ohe_pipe, ohe_columns),
('ord', ord_pipe, ord_columns),
('num', MinMaxScaler(), num_columns),
],
remainder='passthrough'
)
pipe_final = Pipeline([
('preprocessor', data_preprocessor),
('models', DecisionTreeRegressor(random_state=RANDOM_STATE))
])
param_grid = [
{
'models': [KNeighborsRegressor()], # Определение модели KNeighborsRegressor
'models__n_neighbors': range(3, 10), # Количество соседей для рассмотрения
'models__weights': ['uniform', 'distance'], # Тип весов для соседей
'models__algorithm': ['auto', 'brute'], # Алгоритм для поиска ближайших соседей
'models__p': [1, 2], # Параметр мощности для расстояния Минковского (манхэттенское и евклидово расстояние)
'models__metric': ['euclidean', 'manhattan'], # Метрика расстояния для вычисления ближайших соседей
'preprocessor__num': [StandardScaler(), MinMaxScaler(), RobustScaler(), 'passthrough']
},
{
'models': [LinearRegression()],
'preprocessor__num': [StandardScaler(), MinMaxScaler(), RobustScaler(),'passthrough']
},
{
'models': [DecisionTreeRegressor(random_state=RANDOM_STATE)],
'models__min_samples_split': range(2, 5),
#'models__criterion':['squared_error','friedman_mse','absolute_error','poisson'],
'preprocessor__num': [StandardScaler(), MinMaxScaler(), RobustScaler(),'passthrough']
}
]
kf_cv = KFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE)
grid = GridSearchCV(
pipe_final,
param_grid=param_grid,
cv=kf_cv,
scoring=scorer_s,
n_jobs=-1
)
grid.fit(X_train, y_train)
print('Лучшая модель и её параметры:\n\n', grid.best_estimator_)
Лучшая модель и её параметры:
Pipeline(steps=[('preprocessor',
ColumnTransformer(remainder='passthrough',
transformers=[('ohe',
Pipeline(steps=[('simpleImputer_ohe',
SimpleImputer(strategy='most_frequent')),
('ohe',
OneHotEncoder(drop='first',
handle_unknown='ignore',
sparse_output=False))]),
['last_year_promo',
'last_year_violations',
'dept']),
('ord',
Pipeline(steps=[('simpleImputer_befor...
OrdinalEncoder(categories=[['junior',
'middle',
'sinior'],
['medium',
'low',
'high']],
handle_unknown='use_encoded_value',
unknown_value=nan)),
('simpleImputer_after_ord',
SimpleImputer(strategy='most_frequent'))]),
['level', 'workload']),
('num', MinMaxScaler(),
['employment_years', 'salary',
'supervisor_evaluation'])])),
('models',
DecisionTreeRegressor(min_samples_split=4, random_state=42))])
print('Лучшая модель и её параметры:\n\n', grid.best_estimator_)
print(line_1)
print('Параметры лучшей модели:', grid.best_params_)
print(line_1)
print('Метрика лучшей модели по кросс-валидации на обучающих данных:', grid.best_score_*-1)
print(line_1)
Лучшая модель и её параметры:
Pipeline(steps=[('preprocessor',
ColumnTransformer(remainder='passthrough',
transformers=[('ohe',
Pipeline(steps=[('simpleImputer_ohe',
SimpleImputer(strategy='most_frequent')),
('ohe',
OneHotEncoder(drop='first',
handle_unknown='ignore',
sparse_output=False))]),
['last_year_promo',
'last_year_violations',
'dept']),
('ord',
Pipeline(steps=[('simpleImputer_befor...
OrdinalEncoder(categories=[['junior',
'middle',
'sinior'],
['medium',
'low',
'high']],
handle_unknown='use_encoded_value',
unknown_value=nan)),
('simpleImputer_after_ord',
SimpleImputer(strategy='most_frequent'))]),
['level', 'workload']),
('num', MinMaxScaler(),
['employment_years', 'salary',
'supervisor_evaluation'])])),
('models',
DecisionTreeRegressor(min_samples_split=4, random_state=42))])
-----------------------------------------------------------------------------------------------------------------------------
Параметры лучшей модели: {'models': DecisionTreeRegressor(random_state=42), 'models__min_samples_split': 4, 'preprocessor__num': MinMaxScaler()}
-----------------------------------------------------------------------------------------------------------------------------
Метрика лучшей модели по кросс-валидации на обучающих данных: 16.379529982436114
-----------------------------------------------------------------------------------------------------------------------------
print('Метрика SMAPE для лучшей модели:\n', -(grid.best_score_))
Метрика SMAPE для лучшей модели: 16.379529982436114
y_test_pred = grid.predict(X_test)
df = pd.DataFrame({'Actual': y_test, 'Predicted': y_test_pred})
df
| Actual | Predicted | |
|---|---|---|
| id | ||
| 485046 | 0.79 | 0.87 |
| 686555 | 0.72 | 0.68 |
| 467458 | 0.64 | 0.65 |
| 418655 | 0.60 | 0.66 |
| 789145 | 0.75 | 0.76 |
| ... | ... | ... |
| 393147 | 0.56 | 0.50 |
| 109582 | 0.14 | 0.13 |
| 305653 | 0.11 | 0.24 |
| 160233 | 0.43 | 0.55 |
| 771859 | 0.67 | 0.66 |
2000 rows × 2 columns
actual = df['Actual'].tolist()
predicted = df['Predicted'].tolist()
smape = calculate_smape(actual, predicted)
print(f'SMAPE на тестовой выборке: {smape}%')
SMAPE на тестовой выборке: 14.232966108018246%
Найдена лучшая модель. По условиям технического задания метрика должна быть < 15. Т.е наша модель показывает хороший результат
result_job = pd.DataFrame(grid.cv_results_).loc[:, ['params', 'mean_test_score', 'rank_test_score']]
result_job.mean_test_score = result_job.mean_test_score * -1
result_job = result_job.sort_values(by='rank_test_score')
# Конвертация значений в столбце params в строки
result_job['params'] = result_job['params'].astype(str)
result_job.head(10)
| params | mean_test_score | rank_test_score | |
|---|---|---|---|
| 461 | {'models': DecisionTreeRegressor(random_state=42), 'models__min_samples_split': 4, 'preprocessor__num': MinMaxScaler()} | 16.38 | 1 |
| 463 | {'models': DecisionTreeRegressor(random_state=42), 'models__min_samples_split': 4, 'preprocessor__num': 'passthrough'} | 16.38 | 2 |
| 462 | {'models': DecisionTreeRegressor(random_state=42), 'models__min_samples_split': 4, 'preprocessor__num': RobustScaler()} | 16.42 | 3 |
| 460 | {'models': DecisionTreeRegressor(random_state=42), 'models__min_samples_split': 4, 'preprocessor__num': StandardScaler()} | 16.42 | 4 |
| 404 | {'models': KNeighborsRegressor(), 'models__algorithm': 'brute', 'models__metric': 'manhattan', 'models__n_neighbors': 7, 'models__p': 1, 'models__weights': 'distance', 'preprocessor__num': StandardScaler()} | 16.50 | 5 |
| 412 | {'models': KNeighborsRegressor(), 'models__algorithm': 'brute', 'models__metric': 'manhattan', 'models__n_neighbors': 7, 'models__p': 2, 'models__weights': 'distance', 'preprocessor__num': StandardScaler()} | 16.50 | 5 |
| 180 | {'models': KNeighborsRegressor(), 'models__algorithm': 'auto', 'models__metric': 'manhattan', 'models__n_neighbors': 7, 'models__p': 1, 'models__weights': 'distance', 'preprocessor__num': StandardScaler()} | 16.52 | 7 |
| 188 | {'models': KNeighborsRegressor(), 'models__algorithm': 'auto', 'models__metric': 'manhattan', 'models__n_neighbors': 7, 'models__p': 2, 'models__weights': 'distance', 'preprocessor__num': StandardScaler()} | 16.52 | 7 |
| 388 | {'models': KNeighborsRegressor(), 'models__algorithm': 'brute', 'models__metric': 'manhattan', 'models__n_neighbors': 6, 'models__p': 1, 'models__weights': 'distance', 'preprocessor__num': StandardScaler()} | 16.53 | 9 |
| 396 | {'models': KNeighborsRegressor(), 'models__algorithm': 'brute', 'models__metric': 'manhattan', 'models__n_neighbors': 6, 'models__p': 2, 'models__weights': 'distance', 'preprocessor__num': StandardScaler()} | 16.53 | 9 |
С помощью пайплайна была проведена предварительная подготовка, использовано 2 кодировщика, использован ColumnTransformer, обучены 3 модели LogisticRegression(penalty='l1', solver='saga', random_state=RANDOM_STATE), KNeighborsRegressor(), DecisionTreeRegressor(random_state=RANDOM_STATE). С помощью GridSearchCV выбрана лучшая модель и лучшие параметры кней. Это DecisionTreeRegressor(random_state=RANDOM_STATE). Проверили качество лучшей модели с помощью метрики SMAPE, величина которой 14,23% на тестовой выборке. Что свидетельствует о хорошем качестве, полученной нами модели.
Основное различие между логистической регрессией и деревьями решений заключается в том, что логистическая регрессия моделирует взаимосвязь между предикторными переменными и переменной результата в виде линейной функции, в то время как деревья решений создают иерархическую древовидную структуру для моделирования взаимосвязей между переменными.
Проверим нашу модель.
dummy_clf = DummyRegressor(strategy="mean")
dummy_clf.fit(X_train, y_train)
DummyRegressor()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
DummyRegressor()
y_test_pred_dummy = dummy_clf.predict(X_test)
df_dummy = pd.DataFrame({'Actual_d': y_test, 'Predicted_d': y_test_pred_dummy})
df_dummy
| Actual_d | Predicted_d | |
|---|---|---|
| id | ||
| 485046 | 0.79 | 0.53 |
| 686555 | 0.72 | 0.53 |
| 467458 | 0.64 | 0.53 |
| 418655 | 0.60 | 0.53 |
| 789145 | 0.75 | 0.53 |
| ... | ... | ... |
| 393147 | 0.56 | 0.53 |
| 109582 | 0.14 | 0.53 |
| 305653 | 0.11 | 0.53 |
| 160233 | 0.43 | 0.53 |
| 771859 | 0.67 | 0.53 |
2000 rows × 2 columns
actual_d = df_dummy['Actual_d'].tolist()
predicted_d = df_dummy['Predicted_d'].tolist()
smape_d = calculate_smape(actual_d, predicted_d)
print(f'Метрика SMAPE dummy: {smape_d}%')
print(f'Метрика SMAPE на тестовой выборке: {smape}%')
Метрика SMAPE dummy: 38.30438274509703% Метрика SMAPE на тестовой выборке: 14.232966108018246%
Была произведена проверка качества модели регрессии и ее показания значительно превысили метрику на тестовой выборке, что еще раз свидетельствует о хорошем качестве нашей модели.
Посмотрим как распределены признаки по важности, т.е по влиянию на целевой признак.
shap.initjs()
X_test_transformed = grid.best_estimator_.named_steps['preprocessor'].transform(X_test)
model = grid.best_estimator_.named_steps['models'].predict
X_train_transformed = grid.best_estimator_.named_steps['preprocessor'].transform(X_train)
explainer = shap.Explainer(model, X_train_transformed)
shap_values = explainer.shap_values(X_test_transformed)
feature_names =list(map(lambda x: x.split('__')[1][:20],grid.best_estimator_.named_steps['preprocessor'].get_feature_names_out()))
plt.title("Feature Importance - Bar", fontsize=16)
plt.ylabel("Наименование признаков", fontsize=16)
shap.summary_plot(shap_values, X_test_transformed, feature_names=feature_names, plot_type="bar")
plt.title("Feature Importance - Dot", fontsize=16)
plt.ylabel("Наименование признаков", fontsize=16)
shap.summary_plot(shap_values, X_test_transformed, feature_names=feature_names, plot_type="dot");
# Adjust layout and display the plots side by side
plt.tight_layout()
plt.show()
PermutationExplainer explainer: 2001it [00:13, 59.95it/s]
<Figure size 2000x700 with 0 Axes>
Наибольшее влияние на таргет оказывает признак: supervisor_evaluation. чем ниже оценка руководителя тем ниже удовлетворенность работой и наоборот.
try:
df_train_quit = pd.read_csv('/datasets/train_quit.csv', delimiter = ",",decimal=".")
df_test_features = pd.read_csv('/datasets/test_features.csv', delimiter = ",", decimal=".")
df_test_target_quit = pd.read_csv('/datasets/test_target_quit.csv', delimiter = ",", decimal=".")
except:
df_train_quit = pd.read_csv('https://code.s3.yandex.net/datasets/train_quit.csv', delimiter = ",", decimal=".")
df_test_features = pd.read_csv('https://code.s3.yandex.net/datasets/test_features.csv', delimiter = ",", decimal=".")
df_test_target_quit = pd.read_csv('https://code.s3.yandex.net/datasets/test_target_quit.csv', delimiter = ",", decimal=".")
target_col_2 = 'quit'
display(df_train_quit.head())
df_train_quit.tail()
print()
df_train_quit.sample()
df_train_quit.info(memory_usage='deep')
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 723290 | sales | middle | high | 2 | no | no | 4 | 54000 | no |
| 1 | 814010 | sales | junior | medium | 2 | no | no | 4 | 27600 | no |
| 2 | 155091 | purchasing | middle | medium | 5 | no | no | 1 | 37200 | no |
| 3 | 257132 | sales | junior | medium | 2 | no | yes | 3 | 24000 | yes |
| 4 | 910140 | marketing | junior | medium | 2 | no | no | 5 | 25200 | no |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 4000 entries, 0 to 3999 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 4000 non-null int64 1 dept 4000 non-null object 2 level 4000 non-null object 3 workload 4000 non-null object 4 employment_years 4000 non-null int64 5 last_year_promo 4000 non-null object 6 last_year_violations 4000 non-null object 7 supervisor_evaluation 4000 non-null int64 8 salary 4000 non-null int64 9 quit 4000 non-null object dtypes: int64(4), object(6) memory usage: 1.5 MB
Представлена таблица 4000 строк, 9 столбцов, без пропусков, тип данных соответствует. вес 1,5мб
display(df_test_features.head())
df_test_features.tail()
print()
df_test_features.sample()
df_test_features.info(memory_usage='deep')
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 |
| 1 | 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 |
| 2 | 467458 | sales | middle | low | 5 | no | no | 4 | 19200 |
| 3 | 418655 | sales | middle | low | 6 | no | no | 4 | 19200 |
| 4 | 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 2000 entries, 0 to 1999 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 2000 non-null int64 1 dept 1998 non-null object 2 level 1999 non-null object 3 workload 2000 non-null object 4 employment_years 2000 non-null int64 5 last_year_promo 2000 non-null object 6 last_year_violations 2000 non-null object 7 supervisor_evaluation 2000 non-null int64 8 salary 2000 non-null int64 dtypes: int64(4), object(5) memory usage: 662.0 KB
Представлена таблица 2000 строк, 8 столбцов, пропуски в 2 столбцах, тип данных соответствует. вес 662кб.
display(df_test_target_quit.head())
df_test_target_quit.tail()
print()
df_test_target_quit.sample()
df_test_target_quit.info(memory_usage='deep')
| id | quit | |
|---|---|---|
| 0 | 999029 | yes |
| 1 | 372846 | no |
| 2 | 726767 | no |
| 3 | 490105 | no |
| 4 | 416898 | yes |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 2000 entries, 0 to 1999 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 2000 non-null int64 1 quit 2000 non-null object dtypes: int64(1), object(1) memory usage: 131.5 KB
Таблица 2000 строк без пропусков, 2 столбца. 131,5кб
optimize_memory_usage(df_train_quit)
Размер использования памяти: до 0.31 Mb - после 0.22 Mb (27.5%)
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 723290 | sales | middle | high | 2 | no | no | 4 | 54000 | no |
| 1 | 814010 | sales | junior | medium | 2 | no | no | 4 | 27600 | no |
| 2 | 155091 | purchasing | middle | medium | 5 | no | no | 1 | 37200 | no |
| 3 | 257132 | sales | junior | medium | 2 | no | yes | 3 | 24000 | yes |
| 4 | 910140 | marketing | junior | medium | 2 | no | no | 5 | 25200 | no |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3995 | 588809 | sales | junior | medium | 4 | no | no | 3 | 26400 | no |
| 3996 | 672059 | sales | middle | high | 9 | no | no | 4 | 52800 | no |
| 3997 | 536432 | purchasing | junior | low | 2 | no | yes | 4 | 12000 | yes |
| 3998 | 692133 | purchasing | middle | medium | 2 | no | no | 4 | 33600 | no |
| 3999 | 853842 | sales | junior | medium | 2 | no | no | 3 | 27600 | yes |
4000 rows × 10 columns
optimize_memory_usage(df_test_features)
Размер использования памяти: до 0.14 Mb - после 0.10 Mb (30.5%)
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 |
| 1 | 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 |
| 2 | 467458 | sales | middle | low | 5 | no | no | 4 | 19200 |
| 3 | 418655 | sales | middle | low | 6 | no | no | 4 | 19200 |
| 4 | 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1995 | 393147 | marketing | junior | low | 3 | no | no | 3 | 20400 |
| 1996 | 109582 | technology | middle | low | 3 | no | no | 2 | 21600 |
| 1997 | 305653 | technology | junior | low | 1 | no | no | 3 | 14400 |
| 1998 | 160233 | technology | middle | low | 8 | no | no | 1 | 32400 |
| 1999 | 771859 | purchasing | middle | medium | 1 | no | no | 4 | 38400 |
2000 rows × 9 columns
optimize_memory_usage(df_test_target_quit)
Размер использования памяти: до 0.03 Mb - после 0.02 Mb (24.9%)
| id | quit | |
|---|---|---|
| 0 | 999029 | yes |
| 1 | 372846 | no |
| 2 | 726767 | no |
| 3 | 490105 | no |
| 4 | 416898 | yes |
| ... | ... | ... |
| 1995 | 836109 | yes |
| 1996 | 180999 | yes |
| 1997 | 746018 | no |
| 1998 | 247658 | no |
| 1999 | 460332 | yes |
2000 rows × 2 columns
Оптимизировали тип данных. Вес датафреймов уменьшился в среднем на 30%.
разделим столбцы по категориям
num_cols_2 = [col for col in df_train_quit.columns if df_train_quit[col].dtype != 'object']
num_cols_2
['id', 'employment_years', 'supervisor_evaluation', 'salary']
cat_cols_2 = ['dept', 'level', 'workload']
bin_cols_2 = ['last_year_promo', 'last_year_violations', 'quit']
for col in cat_cols_2:
print(f"{col} has {df_train_quit[col].unique()} values\n")
dept has ['sales' 'purchasing' 'marketing' 'technology' 'hr'] values level has ['middle' 'junior' 'sinior'] values workload has ['high' 'medium' 'low'] values
for col in bin_cols_2:
print(f"{col} has {df_train_quit[col].unique()} values\n")
last_year_promo has ['no' 'yes'] values last_year_violations has ['no' 'yes'] values quit has ['no' 'yes'] values
for col in cat_cols_2:
print(f"{col} has {df_test_features[col].unique()} values\n")
dept has ['marketing' 'hr' 'sales' 'purchasing' 'technology' nan ' '] values level has ['junior' 'middle' 'sinior' nan] values workload has ['medium' 'low' 'high' ' '] values
Выявлены аномальные значения в столбце workload
df_test_features['dept'] = df_test_features['dept'].replace(' ', np.nan)
df_test_features['workload'] = df_test_features['workload'].replace(' ', np.nan)
Посмотрим статистику данных
check_data(df_train_quit, 'Train data')
print('-'*100)
check_data(df_test_features, 'Test data')
print('-'*100)
check_data(df_test_target_quit, 'df_test_target_quit');
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 723290 | sales | middle | high | 2 | no | no | 4 | 54000 | no |
| 1 | 814010 | sales | junior | medium | 2 | no | no | 4 | 27600 | no |
| 2 | 155091 | purchasing | middle | medium | 5 | no | no | 1 | 37200 | no |
| 3 | 257132 | sales | junior | medium | 2 | no | yes | 3 | 24000 | yes |
| 4 | 910140 | marketing | junior | medium | 2 | no | no | 5 | 25200 | no |
| id | employment_years | supervisor_evaluation | salary | |
|---|---|---|---|---|
| count | 4000.000000 | 4000.000000 | 4000.000000 | 4000.000000 |
| mean | 552099.283750 | 3.701500 | 3.474750 | 33805.800000 |
| std | 260158.031387 | 2.541852 | 1.004049 | 15152.415163 |
| min | 100222.000000 | 1.000000 | 1.000000 | 12000.000000 |
| 25% | 327785.750000 | 2.000000 | 3.000000 | 22800.000000 |
| 50% | 546673.000000 | 3.000000 | 4.000000 | 30000.000000 |
| 75% | 781497.750000 | 6.000000 | 4.000000 | 43200.000000 |
| max | 999915.000000 | 10.000000 | 5.000000 | 96000.000000 |
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | |
|---|---|---|---|---|---|---|---|---|---|---|
| Уникальные значения | 4000 | 5 | 3 | 3 | 10 | 2 | 2 | 5 | 69 | 2 |
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
----------------------------------------------------------------------------------------------------
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 |
| 1 | 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 |
| 2 | 467458 | sales | middle | low | 5 | no | no | 4 | 19200 |
| 3 | 418655 | sales | middle | low | 6 | no | no | 4 | 19200 |
| 4 | 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 |
| id | employment_years | supervisor_evaluation | salary | |
|---|---|---|---|---|
| count | 2000.000000 | 2000.000000 | 2000.000000 | 2000.000000 |
| mean | 552765.213500 | 3.666500 | 3.526500 | 34066.800000 |
| std | 253851.326129 | 2.537222 | 0.996892 | 15398.436729 |
| min | 100298.000000 | 1.000000 | 1.000000 | 12000.000000 |
| 25% | 339052.000000 | 1.000000 | 3.000000 | 22800.000000 |
| 50% | 550793.000000 | 3.000000 | 4.000000 | 30000.000000 |
| 75% | 765763.750000 | 6.000000 | 4.000000 | 43200.000000 |
| max | 999029.000000 | 10.000000 | 5.000000 | 96000.000000 |
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| Уникальные значения | 2000 | 5 | 3 | 3 | 10 | 2 | 2 | 5 | 66 |
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 3 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
----------------------------------------------------------------------------------------------------
| id | quit | |
|---|---|---|
| 0 | 999029 | yes |
| 1 | 372846 | no |
| 2 | 726767 | no |
| 3 | 490105 | no |
| 4 | 416898 | yes |
| id | |
|---|---|
| count | 2000.000000 |
| mean | 552765.213500 |
| std | 253851.326129 |
| min | 100298.000000 |
| 25% | 339052.000000 |
| 50% | 550793.000000 |
| 75% | 765763.750000 |
| max | 999029.000000 |
| id | quit | |
|---|---|---|
| Уникальные значения | 2000 | 2 |
| id | quit | |
|---|---|---|
| 0 | 0 | 0 |
В тренировочной выборке средний стаж работы составляет 2,5, максимальный 10 лет, наблюдается средняя оценка супервайзера чуть больше 1, максимальная зарплата 96000, средняя 15152
В тестовой выборке средний стаж работы составляет 2,5, максимальный 10 лет, наблюдается средняя оценка супервайзера чуть больше 1, максимальная зарплата 96000, средняя 15398
rename(df_train_quit)
rename(df_test_features)
rename(df_test_target_quit)
| id | quit | |
|---|---|---|
| 0 | 999029 | yes |
| 1 | 372846 | no |
| 2 | 726767 | no |
| 3 | 490105 | no |
| 4 | 416898 | yes |
| ... | ... | ... |
| 1995 | 836109 | yes |
| 1996 | 180999 | yes |
| 1997 | 746018 | no |
| 1998 | 247658 | no |
| 1999 | 460332 | yes |
2000 rows × 2 columns
Будет произведен исследовательский анализ данных из тренировочной выборки. Анализ данных тестовой выборки был произведен в первой задаче, данные не менялись.
Посмотрим на распределение числовых признаков в тренировочной выборке
plot_histogram_with_stats(df_train_quit.drop(['id','employment_years', 'supervisor_evaluation'], axis=1))
# Рассчитываем частоты в процентах
value_counts_normalized = df_train_quit['employment_years'].value_counts(normalize=True).reset_index()
value_counts_normalized.columns = ['employment_years', 'frequency']
# Умножаем на 100, чтобы преобразовать доли в проценты
value_counts_normalized['frequency'] *= 100
plt.figure(figsize=(15, 6))
# Построение графика абсолютных величин в процентах
sns.barplot(x='employment_years', y='frequency', data=value_counts_normalized)
plt.title('Распределение employment_years в процентах')
plt.xlabel('Годы занятости')
plt.ylabel('Процент')
plt.show()
value_counts_normalized = df_train_quit['level'].value_counts(normalize=True).reset_index()
value_counts_normalized.columns = ['level', 'frequency']
# Умножаем на 100, чтобы преобразовать доли в проценты
value_counts_normalized['frequency'] *= 100
plt.figure(figsize=(15, 6))
# Построение графика абсолютных величин в процентах
sns.barplot(x='level', y='frequency', data=value_counts_normalized)
plt.title('Распределение level в процентах')
plt.xlabel('Уровень квалификации')
plt.ylabel('Процент')
plt.show()
value_counts_normalized = df_train_quit['supervisor_evaluation'].value_counts(normalize=True).reset_index()
value_counts_normalized.columns = ['supervisor_evaluation', 'frequency']
# Умножаем на 100, чтобы преобразовать доли в проценты
value_counts_normalized['frequency'] *= 100
plt.figure(figsize=(15, 6))
# Построение графика абсолютных величин в процентах
sns.barplot(x='supervisor_evaluation', y='frequency', data=value_counts_normalized)
plt.title('Распределение supervisor_evaluation в процентах')
plt.xlabel('Оценка руководителя')
plt.ylabel('Процент')
plt.show()
value_counts_normalized = df_train_quit['workload'].value_counts(normalize=True).reset_index()
value_counts_normalized.columns = ['workload', 'frequency']
# Умножаем на 100, чтобы преобразовать доли в проценты
value_counts_normalized['frequency'] *= 100
plt.figure(figsize=(15, 6))
# Построение графика абсолютных величин в процентах
sns.barplot(x='workload', y='frequency', data=value_counts_normalized)
plt.title('Распределение workload в процентах')
plt.xlabel('Уровень загруженности')
plt.ylabel('Процент')
plt.show()
value_counts_normalized = df_train_quit['dept'].value_counts(normalize=True).reset_index()
value_counts_normalized.columns = ['dept', 'frequency']
# Умножаем на 100, чтобы преобразовать доли в проценты
value_counts_normalized['frequency'] *= 100
plt.figure(figsize=(15, 6))
# Построение графика абсолютных величин в процентах
sns.barplot(x='dept', y='frequency', data=value_counts_normalized)
plt.title('Распределение dept в процентах')
plt.xlabel('Отдел')
plt.ylabel('Процент')
plt.show()
df_train_quit.columns
Index(['id', 'dept', 'level', 'workload', 'employment_years',
'last_year_promo', 'last_year_violations', 'supervisor_evaluation',
'salary', 'quit'],
dtype='object')
corr_matrix_df_q = df_train_quit.drop(['id'], axis=1).phik_matrix(interval_cols=['employment_years', 'supervisor_evaluation', 'salary'])
corr_matrix_df_q.round(2)
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | |
|---|---|---|---|---|---|---|---|---|---|
| dept | 1.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.04 | 0.04 | 0.28 | 0.00 |
| level | 0.00 | 1.00 | 0.48 | 0.69 | 0.11 | 0.00 | 0.01 | 0.75 | 0.31 |
| workload | 0.00 | 0.48 | 1.00 | 0.21 | 0.04 | 0.01 | 0.00 | 0.79 | 0.13 |
| employment_years | 0.00 | 0.69 | 0.21 | 1.00 | 0.16 | 0.00 | 0.00 | 0.49 | 0.66 |
| last_year_promo | 0.00 | 0.11 | 0.04 | 0.16 | 1.00 | 0.00 | 0.03 | 0.22 | 0.16 |
| last_year_violations | 0.04 | 0.00 | 0.01 | 0.00 | 0.00 | 1.00 | 0.18 | 0.09 | 0.19 |
| supervisor_evaluation | 0.04 | 0.01 | 0.00 | 0.00 | 0.03 | 0.18 | 1.00 | 0.06 | 0.25 |
| salary | 0.28 | 0.75 | 0.79 | 0.49 | 0.22 | 0.09 | 0.06 | 1.00 | 0.56 |
| quit | 0.00 | 0.31 | 0.13 | 0.66 | 0.16 | 0.19 | 0.25 | 0.56 | 1.00 |
plot_correlation_matrix(
corr_matrix_df_q.values,
x_labels=corr_matrix_df_q.columns,
y_labels=corr_matrix_df_q.index,
vmin=0, vmax=1, color_map='PuOr',
title=r'Корреляция $\phi_K$',
fontsize_factor=1.5,
figsize=(20, 15)
)
Признаки из задачи №2 по зависимости распределены аналогично признакам из задачи №1: на
d_2 = df_train_quit[['employment_years','supervisor_evaluation','salary', 'quit']]
sns.pairplot(d, diag_kind = 'kde', hue = 'supervisor_evaluation', plot_kws={'alpha':0.5, 'edgecolor': 'k'})
<seaborn.axisgrid.PairGrid at 0x1f563583650>
Посмотрим распределение категориальных признаков
plot_feature(df_train_quit, 'dept', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_train_quit, 'level', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_train_quit, 'workload', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_train_quit, 'last_year_promo', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_train_quit, 'last_year_violations', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_train_quit, 'quit', figsize=(16,5), palette='colorblind', name='Train data')
Сделаем срез сотрудников по признаку увольнения и проанализируем уволившихся сотрудников
df_train_quit_yes = df_train_quit[df_train_quit["quit"] == "yes"]
df_train_quit_no = df_train_quit[df_train_quit["quit"] == "no"]
На основании визуализаций распределения категориальных признаков составим портрет сотрудника по новым данным: это сотрудник отдела продаж, уровня junior, сильнозагруженного, малая доля получавших вознаграждение, иногда совершавшего нарушения. Всего количество уволившихся составило 28%.
plot_feature(df_train_quit_yes, 'dept', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_train_quit_yes, 'level', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_train_quit_yes, 'workload', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_train_quit_yes, 'last_year_promo', figsize=(16,5), palette='colorblind', name='Train data')
plot_feature(df_train_quit_yes, 'last_year_violations', figsize=(16,5), palette='colorblind', name='Train data')
На основании визуализаций распределения категориальных признаков составим портрет уволившегося сотрудника: это сотрудник отдела продаж, уровня junior, сильнозагруженного, не получавшего за последний год повышения, не совершавшего нарушений. Всего количество уволившихся составило 28%. Ниже проведем анализ по уволившимся сотрудникам по оставшимся признакам: уровень, заработной платы, оценка руководителя.
Посмотрим имеется ли связь между двумя целевыми признаками: удовлетворенность работой и увольнением
df_test_target_quit = (df_test_target.merge(df_test_target_quit, on="id").reset_index(drop=True))
df_test_target_quit
| id | job_satisfaction_rate | quit | |
|---|---|---|---|
| 0 | 130604 | 0.74 | no |
| 1 | 825977 | 0.75 | no |
| 2 | 418490 | 0.60 | yes |
| 3 | 555320 | 0.72 | no |
| 4 | 826430 | 0.08 | yes |
| ... | ... | ... | ... |
| 1995 | 405257 | 0.28 | no |
| 1996 | 370925 | 0.21 | yes |
| 1997 | 857722 | 0.39 | no |
| 1998 | 303402 | 0.26 | yes |
| 1999 | 648995 | 0.62 | no |
2000 rows × 3 columns
facet = sns.FacetGrid(df_test_target_quit, hue="quit",aspect=4)
facet.map(sns.kdeplot,'job_satisfaction_rate',shade= True)
facet.set(xlim=(0, df_test_target_quit['job_satisfaction_rate'].max()))
facet.add_legend()
plt.ylabel('Плотность', fontsize=12)
plt.title('Рапределение уволившихся сотрудников по признаку удовлетворенности работой (max)')
# average survived passengers by age
fig, axis1 = plt.subplots(1,1,figsize=(18,4))
average_age = df_test_target_quit[["job_satisfaction_rate", "quit"]].groupby(['quit'],as_index=False).mean()
sns.barplot(x='job_satisfaction_rate', y='quit', data=average_age)
plt.title('Рапределение уволившихся сотрудников по признаку удовлетворенности работой (mean)')
Text(0.5, 1.0, 'Рапределение уволившихся сотрудников по признаку удовлетворенности работой (mean)')
z = sns.histplot(x = 'job_satisfaction_rate', hue = 'quit', data = df_test_target_quit, stat='density', kde=True, common_norm=False, bins = 100);
#z.set_xlabel("job_satisfaction_rate")
plt.title('Рапределение уволившихся сотрудников по признаку удовлетворенности работой')
plt.show()
Приведенный график показывает, что между признаками удовлетворенность работой и увольнение прослеживается связь: большинство уволившихся имеют степень удовлетворенности работой <= 0.5. Прослеживается два пика, пик уволившихся приходится на уровень удовлетворенности работой 0.3-0.35, а пик неуволившихся на уровень job_satisfaction_rate 0.6-0.8. И еще один пик уволившихся сотрудников приходится на уровень удовлетворенности работой 0.68. Средний уровень удовлетворенности уволившегося сотрудника 0.38
print("Количество строк в 'df_train_quit_yes':", df_train_quit_yes['id'].count())
print("Количество строк в 'df_train_quit_no':", df_train_quit_no['id'].count())
print()
print("Количество уникальных пользователей в 'df_train_quit_yes':", len(df_train_quit_yes['id'].unique()))
print("Количество уникальных пользователей в 'df_train_quit_yes':", len(df_train_quit_no['id'].unique()))
Количество строк в 'df_train_quit_yes': 1128 Количество строк в 'df_train_quit_no': 2872 Количество уникальных пользователей в 'df_train_quit_yes': 1128 Количество уникальных пользователей в 'df_train_quit_yes': 2872
Важно понять, была заработная плата уволившихся сотрудников меньше чем у оставшихся сотрудников
Проверим гипотезу:
H0: зарплата неуволившихся сотрудников = зарплата уволившихся сотрудников (нулевая гитпотеза)
H1: зарплата неуволившихся сотрудников > зарплата уволившихся сотрудников (нулевая гитпотеза)
alpha = 0.05
sub_good = df_train_quit_no['salary']
sub_bad = df_train_quit_yes['salary']
results = st.ttest_ind(sub_good,
sub_bad,
alternative = 'greater')
alpha = 0.05
print('Средняя зарплата оставшегося сотрудника:', pd.Series(sub_good).mean())
print('Средняя зарплата уволившегося сотрудника:', pd.Series(sub_bad).mean())
print('p-значение:', results.pvalue)
if results.pvalue < alpha:
print('Отвергаем нулевую гипотезу')
else:
print('Не удалось отвергнуть нулевую гипотезу')
Средняя зарплата оставшегося сотрудника: 37702.22841225627 Средняя зарплата уволившегося сотрудника: 23885.106382978724 p-значение: 1.232068933994945e-162 Отвергаем нулевую гипотезу
Средняя заработная плата уволившегося сотрудника действительно меньше заработной платы оставшихся сотрудников
Важно понять, была оценка руководителя уволившихся сотрудников ниже чем у оставшихся сотрудников
Проверим гипотезу:
H0: оценка руководителя неуволившихся сотрудников = оценка руководителя уволившихся сотрудников (нулевая гитпотеза)
H1: оценка руководителя неуволившихся сотрудников > оценка руководителя уволившихся сотрудников (нулевая гитпотеза)
alpha = 0.05
sub_good_2 = df_train_quit_no['supervisor_evaluation']
sub_bad_2 = df_train_quit_yes['supervisor_evaluation']
results = st.ttest_ind(sub_good_2,
sub_bad_2,
alternative = 'greater')
alpha = 0.05
print('Средняя оценка руководителя оставшегося сотрудника:', pd.Series(sub_good_2).mean())
print('Средняя оценка руководителя уволившегося сотрудника:', pd.Series(sub_bad_2).mean())
print('p-значение:', results.pvalue)
if results.pvalue < alpha:
print('Отвергаем нулевую гипотезу')
else:
print('Не удалось отвергнуть нулевую гипотезу')
Средняя оценка руководителя оставшегося сотрудника: 3.643105849582173 Средняя оценка руководителя уволившегося сотрудника: 3.0460992907801416 p-значение: 7.42906192784865e-67 Отвергаем нулевую гипотезу
Средняя оценка руководителя уволившегося сотрудника действительно меньше средней оценка руководителя оставшихся сотрудников
В дополнение к ранее сделанным выводам об уволившихся сотрудниках добавим, что статистический тест подтвердил наши выводы о том, что средняя заработная плата уволившихся сотрудников ниже чем у сотрудников оставшихся, как и оценка руководителя.
Исходя из выводов о наличии связи между двумя целевыми признаками добавим признак удовлетворения работой, предсказанный по условиям первой задачи к входным признакам второй задачи.
df_train_quit
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 723290 | sales | middle | high | 2 | no | no | 4 | 54000 | no |
| 1 | 814010 | sales | junior | medium | 2 | no | no | 4 | 27600 | no |
| 2 | 155091 | purchasing | middle | medium | 5 | no | no | 1 | 37200 | no |
| 3 | 257132 | sales | junior | medium | 2 | no | yes | 3 | 24000 | yes |
| 4 | 910140 | marketing | junior | medium | 2 | no | no | 5 | 25200 | no |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3995 | 588809 | sales | junior | medium | 4 | no | no | 3 | 26400 | no |
| 3996 | 672059 | sales | middle | high | 9 | no | no | 4 | 52800 | no |
| 3997 | 536432 | purchasing | junior | low | 2 | no | yes | 4 | 12000 | yes |
| 3998 | 692133 | purchasing | middle | medium | 2 | no | no | 4 | 33600 | no |
| 3999 | 853842 | sales | junior | medium | 2 | no | no | 3 | 27600 | yes |
4000 rows × 10 columns
df_train_quit_pr = df_train_quit.set_index('id')
df_train_quit_pr
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | |
|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||
| 723290 | sales | middle | high | 2 | no | no | 4 | 54000 | no |
| 814010 | sales | junior | medium | 2 | no | no | 4 | 27600 | no |
| 155091 | purchasing | middle | medium | 5 | no | no | 1 | 37200 | no |
| 257132 | sales | junior | medium | 2 | no | yes | 3 | 24000 | yes |
| 910140 | marketing | junior | medium | 2 | no | no | 5 | 25200 | no |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 588809 | sales | junior | medium | 4 | no | no | 3 | 26400 | no |
| 672059 | sales | middle | high | 9 | no | no | 4 | 52800 | no |
| 536432 | purchasing | junior | low | 2 | no | yes | 4 | 12000 | yes |
| 692133 | purchasing | middle | medium | 2 | no | no | 4 | 33600 | no |
| 853842 | sales | junior | medium | 2 | no | no | 3 | 27600 | yes |
4000 rows × 9 columns
df_train_quit_pr_no = df_train_quit_pr.drop(columns=['quit'], axis=1)
df_train_quit_pr_no
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|
| id | ||||||||
| 723290 | sales | middle | high | 2 | no | no | 4 | 54000 |
| 814010 | sales | junior | medium | 2 | no | no | 4 | 27600 |
| 155091 | purchasing | middle | medium | 5 | no | no | 1 | 37200 |
| 257132 | sales | junior | medium | 2 | no | yes | 3 | 24000 |
| 910140 | marketing | junior | medium | 2 | no | no | 5 | 25200 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 588809 | sales | junior | medium | 4 | no | no | 3 | 26400 |
| 672059 | sales | middle | high | 9 | no | no | 4 | 52800 |
| 536432 | purchasing | junior | low | 2 | no | yes | 4 | 12000 |
| 692133 | purchasing | middle | medium | 2 | no | no | 4 | 33600 |
| 853842 | sales | junior | medium | 2 | no | no | 3 | 27600 |
4000 rows × 8 columns
y_test_pred_2 = grid.predict(df_train_quit_pr_no)
df_job = pd.DataFrame({'Pred_job': y_test_pred_2})
df_job
| Pred_job | |
|---|---|
| 0 | 0.65 |
| 1 | 0.88 |
| 2 | 0.38 |
| 3 | 0.38 |
| 4 | 0.69 |
| ... | ... |
| 3995 | 0.55 |
| 3996 | 0.86 |
| 3997 | 0.55 |
| 3998 | 0.55 |
| 3999 | 0.52 |
4000 rows × 1 columns
df_train_quit_pr['job_satisfaction_rate'] = y_test_pred_2
df_train_quit_pr
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| id | ||||||||||
| 723290 | sales | middle | high | 2 | no | no | 4 | 54000 | no | 0.65 |
| 814010 | sales | junior | medium | 2 | no | no | 4 | 27600 | no | 0.88 |
| 155091 | purchasing | middle | medium | 5 | no | no | 1 | 37200 | no | 0.38 |
| 257132 | sales | junior | medium | 2 | no | yes | 3 | 24000 | yes | 0.38 |
| 910140 | marketing | junior | medium | 2 | no | no | 5 | 25200 | no | 0.69 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 588809 | sales | junior | medium | 4 | no | no | 3 | 26400 | no | 0.55 |
| 672059 | sales | middle | high | 9 | no | no | 4 | 52800 | no | 0.86 |
| 536432 | purchasing | junior | low | 2 | no | yes | 4 | 12000 | yes | 0.55 |
| 692133 | purchasing | middle | medium | 2 | no | no | 4 | 33600 | no | 0.55 |
| 853842 | sales | junior | medium | 2 | no | no | 3 | 27600 | yes | 0.52 |
4000 rows × 10 columns
df_test_features
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 |
| 1 | 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 |
| 2 | 467458 | sales | middle | low | 5 | no | no | 4 | 19200 |
| 3 | 418655 | sales | middle | low | 6 | no | no | 4 | 19200 |
| 4 | 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1995 | 393147 | marketing | junior | low | 3 | no | no | 3 | 20400 |
| 1996 | 109582 | technology | middle | low | 3 | no | no | 2 | 21600 |
| 1997 | 305653 | technology | junior | low | 1 | no | no | 3 | 14400 |
| 1998 | 160233 | technology | middle | low | 8 | no | no | 1 | 32400 |
| 1999 | 771859 | purchasing | middle | medium | 1 | no | no | 4 | 38400 |
2000 rows × 9 columns
df_test_features_job = df_test_features.set_index('id')
y_test_pred_2_test = grid.predict(df_test_features_job)
df_test_features_job['job_satisfaction_rate'] = y_test_pred_2_test
df_test_features_job
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||
| 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 | 0.87 |
| 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 | 0.68 |
| 467458 | sales | middle | low | 5 | no | no | 4 | 19200 | 0.65 |
| 418655 | sales | middle | low | 6 | no | no | 4 | 19200 | 0.66 |
| 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 | 0.76 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 393147 | marketing | junior | low | 3 | no | no | 3 | 20400 | 0.50 |
| 109582 | technology | middle | low | 3 | no | no | 2 | 21600 | 0.13 |
| 305653 | technology | junior | low | 1 | no | no | 3 | 14400 | 0.24 |
| 160233 | technology | middle | low | 8 | no | no | 1 | 32400 | 0.55 |
| 771859 | purchasing | middle | medium | 1 | no | no | 4 | 38400 | 0.66 |
2000 rows × 9 columns
Во входные данные добавлен новый признак: предсказанное с помощью первой модели значение удовлетворенности работой.
duplicateRows_train = df_train_quit_pr[df_train_quit_pr.duplicated ()]
df_train_quit_pr = df_train_quit_pr.drop_duplicates (keep= False)
df_train_quit_pr.describe(include='all')
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| count | 1934 | 1934 | 1934 | 1934.00 | 1934 | 1934 | 1934.00 | 1934.00 | 1934 | 1934.00 |
| unique | 5 | 3 | 3 | NaN | 2 | 2 | NaN | NaN | 2 | NaN |
| top | sales | middle | medium | NaN | no | no | NaN | NaN | no | NaN |
| freq | 567 | 1015 | 872 | NaN | 1825 | 1512 | NaN | NaN | 1479 | NaN |
| mean | NaN | NaN | NaN | 4.50 | NaN | NaN | 3.30 | 39528.65 | NaN | 0.50 |
| std | NaN | NaN | NaN | 2.63 | NaN | NaN | 1.12 | 16835.26 | NaN | 0.23 |
| min | NaN | NaN | NaN | 1.00 | NaN | NaN | 1.00 | 12000.00 | NaN | 0.03 |
| 25% | NaN | NaN | NaN | 2.00 | NaN | NaN | 3.00 | 25500.00 | NaN | 0.33 |
| 50% | NaN | NaN | NaN | 4.00 | NaN | NaN | 3.00 | 37200.00 | NaN | 0.50 |
| 75% | NaN | NaN | NaN | 6.00 | NaN | NaN | 4.00 | 50400.00 | NaN | 0.69 |
| max | NaN | NaN | NaN | 10.00 | NaN | NaN | 5.00 | 96000.00 | NaN | 1.00 |
df_train_quit_pr['workload'].unique()
array(['medium', 'low', 'high'], dtype=object)
Для исследования датафрейма после добавления нового признака сделаем копию и переведем некоторые категориальные признаки в числовые
df=df_train_quit_pr.copy()
df['level']=df['level'].replace('junior','0')
df['level']=df['level'].replace('middle','1')
df['level']=df['level'].replace('sinior','2')
df['workload']=df['workload'].replace('medium','0')
df['workload']=df['workload'].replace('low','1')
df['workload']=df['workload'].replace('high','2')
df=df.replace('no','0')
df= df.replace('yes','1')
df
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| id | ||||||||||
| 910140 | marketing | 0 | 0 | 2 | 0 | 0 | 5 | 25200 | 0 | 0.69 |
| 699916 | sales | 0 | 1 | 1 | 0 | 1 | 3 | 18000 | 1 | 0.18 |
| 417070 | technology | 1 | 0 | 8 | 0 | 0 | 2 | 44400 | 0 | 0.40 |
| 165489 | sales | 1 | 1 | 4 | 0 | 0 | 5 | 19200 | 0 | 0.47 |
| 613206 | technology | 1 | 0 | 2 | 0 | 0 | 3 | 45600 | 1 | 0.31 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 778735 | technology | 0 | 1 | 2 | 0 | 0 | 4 | 22800 | 1 | 0.84 |
| 106936 | marketing | 0 | 1 | 1 | 0 | 0 | 5 | 18000 | 0 | 0.86 |
| 775320 | technology | 0 | 2 | 1 | 0 | 0 | 4 | 42000 | 1 | 0.54 |
| 536432 | purchasing | 0 | 1 | 2 | 0 | 1 | 4 | 12000 | 1 | 0.55 |
| 692133 | purchasing | 1 | 0 | 2 | 0 | 0 | 4 | 33600 | 0 | 0.55 |
1934 rows × 10 columns
df.isnull().sum()
dept 0 level 0 workload 0 employment_years 0 last_year_promo 0 last_year_violations 0 supervisor_evaluation 0 salary 0 quit 0 job_satisfaction_rate 0 dtype: int64
df.dtypes
dept object level object workload object employment_years int8 last_year_promo object last_year_violations object supervisor_evaluation int8 salary int32 quit object job_satisfaction_rate float64 dtype: object
colors = [ 'Blue', 'Cyan',"navy",'LightCyan',]
CustomPalette = sns.set_palette(sns.color_palette(colors))
dfcolumns=[ 'dept', 'level', 'workload', 'last_year_promo',
'last_year_violations', 'supervisor_evaluation', 'salary', 'job_satisfaction_rate']
plt.subplots_adjust(left=0.3, bottom=0.1, right=0.9, top=0.7, wspace=0.2, hspace=0.3)
for i, col1 in enumerate(dfcolumns):
if col1=="dept":
continue
else:
#for j, col2 in enumerate(numericaldf):
fig , axes= plt.subplots(1,2, figsize=(17,2),gridspec_kw={'width_ratios': [2.25, 1.25]})
ax=plt.subplot(1,2,1 )
ax0=sns.distplot(df[col1][df['quit']=="0"],hist=False, kde=True,label='quit = No' )
ax1=sns.distplot(df[col1][df['quit']=="1"],hist=False, kde=True , ax=ax0, label='quit= Yes')
# Get the two lines from the axes to generate shading
l1 = ax.lines[0]
l2 = ax.lines[1]
# Get the xy data from the lines so that we can shade
x1 = l1.get_xydata()[:,0]
y1 = l1.get_xydata()[:,1]
x2 = l2.get_xydata()[:,0]
y2 = l2.get_xydata()[:,1]
ax.fill_between(x1,y1, color="blue", alpha=0.3)
ax.fill_between(x2,y2, color="aqua", alpha=0.3)
#plt.legend(loc='upper right')
ax=plt.subplot(1,2,2)
ax2=sns.histplot(data=df, x=df[col1], hue=df['quit'], multiple="stack")
for container in ax.containers:
if col1=="supervisor_evaluation":
continue
elif col1== "salary":
continue
elif col1== "job_satisfaction_rate":
continue
else:
ax.bar_label(container)
plt.show()
<Figure size 2000x700 with 0 Axes>
fig, ax = plt.subplots(figsize = (14,6))
sns.kdeplot(df[df["last_year_promo"]=='1']["job_satisfaction_rate"], alpha=1,shade = False,color="#FF0000" , label="last_year_promo", ax = ax)
sns.kdeplot(df[df["last_year_violations"]=='1']["job_satisfaction_rate"], alpha=1,shade = False,color="#00FF00" , label="last_year_violations", ax = ax)
sns.kdeplot(df[df["quit"]=='1']["job_satisfaction_rate"], alpha=1,shade = False,color="#0000FF" , label="quit", ax = ax)
ax.set_xlabel("job_satisfaction_rate")
ax.set_ylabel("Frequency")
ax.legend(bbox_to_anchor=(1.02, 1), loc=2, borderaxespad=0.)
plt.show()
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df1 = df.copy(deep = True)
col = list(df.columns)
categorical_features = []
numerical_features = []
for i in df1.columns:
if len(df[i].unique()) > 6:
numerical_features.append(i)
else:
df1[i]=le.fit_transform(df1[i])
categorical_features.append(i)
print('Categorical Features :',*categorical_features)
print('Numerical Features :',*numerical_features)
Categorical Features : dept level workload last_year_promo last_year_violations supervisor_evaluation quit Numerical Features : employment_years salary job_satisfaction_rate
df_orginal=df_train_quit_pr
df_train_quit_pr['dept'].unique()
array(['marketing', 'sales', 'technology', 'hr', 'purchasing'],
dtype=object)
color_ye=['Blue', 'Cyan',"navy",'LightCyan','#17becf','#6495ED']
CustomPalette_ye = sns.set_palette(sns.color_palette(color_ye))
color_no=["red","LightSalmon","orange","yellow",'#8B0000','#FFA07A']
CustomPalette_ye = sns.set_palette(sns.color_palette(color_no))
dept = df_orginal[df_orginal['quit'] == "yes"]['dept'].value_counts()
labels_dept = df_orginal[df_orginal['quit'] == "yes"]['dept'].value_counts().index
level = df_orginal[df_orginal['quit'] == "yes"]['level'].value_counts()
labels_level = df_orginal[df_orginal['quit'] == "yes"]['level'].value_counts().index
workload = df_orginal[df_orginal['quit'] == "yes"]['workload'].value_counts()
labels_workload = df_orginal[df_orginal['quit'] == "yes"]['workload'].value_counts().index
last_year_promo = df_orginal[df_orginal['quit'] == "yes"]['last_year_promo'].value_counts()
labels_last_year_promo = df_orginal[df_orginal['quit'] == "yes"]['last_year_promo'].value_counts().index
last_year_violations = df_orginal[df_orginal['quit'] == "yes"]['last_year_violations'].value_counts()
labels_last_year_violations = df_orginal[df_orginal['quit'] == "yes"]['last_year_violations'].value_counts().index
supervisor_evaluation =df_orginal[df_orginal['quit'] == "yes"]['supervisor_evaluation'].value_counts()
labels_supervisor_evaluation = df_orginal[df_orginal['quit'] == "yes"]['supervisor_evaluation'].value_counts().index
###########
deptn = df_orginal[df_orginal['quit'] == "no"]['dept'].value_counts()
labels_dept = df_orginal[df_orginal['quit'] == "yes"]['dept'].value_counts().index
leveln = df_orginal[df_orginal['quit'] == "no"]['level'].value_counts()
labels_level = df_orginal[df_orginal['quit'] == "yes"]['level'].value_counts().index
workloadn = df_orginal[df_orginal['quit'] == "no"]['workload'].value_counts()
labels_workload = df_orginal[df_orginal['quit'] == "yes"]['workload'].value_counts().index
last_year_promon = df_orginal[df_orginal['quit'] == "no"]['last_year_promo'].value_counts()
labels_last_year_promo = df_orginal[df_orginal['quit'] == "yes"]['last_year_promo'].value_counts().index
last_year_violationsn = df_orginal[df_orginal['quit'] == "no"]['last_year_violations'].value_counts()
labels_last_year_violations = df_orginal[df_orginal['quit'] == "yes"]['last_year_violations'].value_counts().index
supervisor_evaluationn =df_orginal[df_orginal['quit'] == "no"]['supervisor_evaluation'].value_counts()
labels_supervisor_evaluation = df_orginal[df_orginal['quit'] == "yes"]['supervisor_evaluation'].value_counts().index
#########
ax,fig = plt.subplots(nrows = 3,ncols = 4,figsize = (20,20))
plt.subplot(3,4,1)
plt.pie(dept,labels =labels_dept,colors = color_ye, rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('dept');
plt.subplot(3,4,2)
plt.pie(level,labels = labels_level,colors = color_ye, rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('level');
plt.subplot(3,4,3)
plt.pie(deptn,labels = labels_dept,colors = color_no,rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('dept');
plt.subplot(3,4,4)
plt.pie(leveln,labels =labels_level,colors = color_no,rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('level');
plt.subplot(3,4,5)
plt.pie(workload,labels =labels_workload,colors = color_ye, rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('workload');
plt.subplot(3,4,6)
plt.pie(last_year_promo,labels = labels_last_year_promo,colors = color_ye, rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('last_year_promo');
plt.subplot(3,4,7)
plt.pie(workload,labels =labels_workload,colors = color_no, rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('workload');
plt.subplot(3,4,8)
plt.pie(last_year_promon,labels = labels_last_year_promo,colors = color_no, rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('last_year_promo');
plt.subplot(3,4,9)
plt.pie(last_year_violations,labels = labels_last_year_violations,colors = color_ye,rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('last_year_violations');
plt.subplot(3,4,10)
plt.pie(supervisor_evaluation,labels = labels_supervisor_evaluation,colors = color_ye,rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('supervisor_evaluation');
plt.subplot(3,4,11)
plt.pie(last_year_violationsn,labels = labels_last_year_violations,colors = color_no,rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('last_year_violations');
plt.subplot(3,4,12)
plt.pie(supervisor_evaluationn,labels = labels_supervisor_evaluation,colors = color_no,rotatelabels=False, autopct='%1.f%%',shadow=True, startangle=9)
plt.title('supervisor_evaluation');
plt.figtext(0.3,0.95,"quit : Yes", va="center", ha="center", size=20)
plt.figtext(0.7,0.95,"quit: No", va="center", ha="center", size=20)
plt.suptitle('quit',fontsize=20)
plt.subplots_adjust(top=0.92)
plt.show()
Анализ данных позволяет сделать вывод, что факторы, такие как низкая зарплата, отсутствие карьерного роста и высокая текучесть на начальных позициях, могут способствовать увольнениям. Удовлетворенность работой варьируется в зависимости от отдела и уровня карьеры, при этом сотрудники с повышениями и без нарушений демонстрируют более высокую удовлетворенность. Для повышения удовлетворенности и уменьшения текучести кадров рекомендуется сконцентрироваться на политике вознаграждений, возможностях карьерного роста и оптимизации рабочей нагрузки.
df_train_quit_pr.columns = df_train_quit_pr.columns.str.strip().str.lower().str.replace(' ', '_').str.replace('(', '').str.replace(')', '')
df_train_quit_pr.head()
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | quit | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|---|
| id | ||||||||||
| 910140 | marketing | junior | medium | 2 | no | no | 5 | 25200 | no | 0.69 |
| 699916 | sales | junior | low | 1 | no | yes | 3 | 18000 | yes | 0.18 |
| 417070 | technology | middle | medium | 8 | no | no | 2 | 44400 | no | 0.40 |
| 165489 | sales | middle | low | 4 | no | no | 5 | 19200 | no | 0.47 |
| 613206 | technology | middle | medium | 2 | no | no | 3 | 45600 | yes | 0.31 |
df_test_features_job.columns = df_test_features_job.columns.str.strip().str.lower().str.replace(' ', '_').str.replace('(', '').str.replace(')', '')
df_test_features_job.head()
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||
| 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 | 0.87 |
| 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 | 0.68 |
| 467458 | sales | middle | low | 5 | no | no | 4 | 19200 | 0.65 |
| 418655 | sales | middle | low | 6 | no | no | 4 | 19200 | 0.66 |
| 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 | 0.76 |
df_train_quit_pr[
df_train_quit_pr.select_dtypes(["object"]).columns
] = df_train_quit_pr.select_dtypes(["object"]).apply(lambda x: x.astype("category"))
df_test_features_job[
df_test_features_job.select_dtypes(["object"]).columns
] = df_test_features_job.select_dtypes(["object"]).apply(lambda x: x.astype("category"))
df_test_target_quit
| id | job_satisfaction_rate | quit | |
|---|---|---|---|
| 0 | 130604 | 0.74 | no |
| 1 | 825977 | 0.75 | no |
| 2 | 418490 | 0.60 | yes |
| 3 | 555320 | 0.72 | no |
| 4 | 826430 | 0.08 | yes |
| ... | ... | ... | ... |
| 1995 | 405257 | 0.28 | no |
| 1996 | 370925 | 0.21 | yes |
| 1997 | 857722 | 0.39 | no |
| 1998 | 303402 | 0.26 | yes |
| 1999 | 648995 | 0.62 | no |
2000 rows × 3 columns
df_test_target_quit = df_test_target_quit.set_index('id')
df_test_target_quit = df_test_target_quit.drop(columns=['job_satisfaction_rate'], axis=1)
df_test_target_quit
| quit | |
|---|---|
| id | |
| 130604 | no |
| 825977 | no |
| 418490 | yes |
| 555320 | no |
| 826430 | yes |
| ... | ... |
| 405257 | no |
| 370925 | yes |
| 857722 | no |
| 303402 | yes |
| 648995 | no |
2000 rows × 1 columns
df_test_features_job
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | |
|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||
| 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 | 0.87 |
| 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 | 0.68 |
| 467458 | sales | middle | low | 5 | no | no | 4 | 19200 | 0.65 |
| 418655 | sales | middle | low | 6 | no | no | 4 | 19200 | 0.66 |
| 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 | 0.76 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 393147 | marketing | junior | low | 3 | no | no | 3 | 20400 | 0.50 |
| 109582 | technology | middle | low | 3 | no | no | 2 | 21600 | 0.13 |
| 305653 | technology | junior | low | 1 | no | no | 3 | 14400 | 0.24 |
| 160233 | technology | middle | low | 8 | no | no | 1 | 32400 | 0.55 |
| 771859 | purchasing | middle | medium | 1 | no | no | 4 | 38400 | 0.66 |
2000 rows × 9 columns
df_test_features_job_pr = (df_test_features_job.merge(df_test_target_quit, on="id").reset_index(drop=False))
df_test_features_job_pr
| id | dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | quit | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 | 0.87 | no |
| 1 | 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 | 0.68 | no |
| 2 | 467458 | sales | middle | low | 5 | no | no | 4 | 19200 | 0.65 | no |
| 3 | 418655 | sales | middle | low | 6 | no | no | 4 | 19200 | 0.66 | no |
| 4 | 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 | 0.76 | no |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1995 | 393147 | marketing | junior | low | 3 | no | no | 3 | 20400 | 0.50 | yes |
| 1996 | 109582 | technology | middle | low | 3 | no | no | 2 | 21600 | 0.13 | yes |
| 1997 | 305653 | technology | junior | low | 1 | no | no | 3 | 14400 | 0.24 | yes |
| 1998 | 160233 | technology | middle | low | 8 | no | no | 1 | 32400 | 0.55 | no |
| 1999 | 771859 | purchasing | middle | medium | 1 | no | no | 4 | 38400 | 0.66 | no |
2000 rows × 11 columns
df_test_features_job_pr = df_test_features_job_pr.set_index('id')
df_test_features_job_pr
| dept | level | workload | employment_years | last_year_promo | last_year_violations | supervisor_evaluation | salary | job_satisfaction_rate | quit | |
|---|---|---|---|---|---|---|---|---|---|---|
| id | ||||||||||
| 485046 | marketing | junior | medium | 2 | no | no | 5 | 28800 | 0.87 | no |
| 686555 | hr | junior | medium | 1 | no | no | 4 | 30000 | 0.68 | no |
| 467458 | sales | middle | low | 5 | no | no | 4 | 19200 | 0.65 | no |
| 418655 | sales | middle | low | 6 | no | no | 4 | 19200 | 0.66 | no |
| 789145 | hr | middle | medium | 5 | no | no | 5 | 40800 | 0.76 | no |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 393147 | marketing | junior | low | 3 | no | no | 3 | 20400 | 0.50 | yes |
| 109582 | technology | middle | low | 3 | no | no | 2 | 21600 | 0.13 | yes |
| 305653 | technology | junior | low | 1 | no | no | 3 | 14400 | 0.24 | yes |
| 160233 | technology | middle | low | 8 | no | no | 1 | 32400 | 0.55 | no |
| 771859 | purchasing | middle | medium | 1 | no | no | 4 | 38400 | 0.66 | no |
2000 rows × 10 columns
Закодируем целевой признак для дальнейшей работы: сотрудник не уволился:0, уволился: 1.
le = preprocessing.LabelEncoder()
trained_le = le.fit(df_train_quit_pr['quit'])
df_train_quit_pr['quit'] = trained_le.transform(df_train_quit_pr['quit'])
df_test_features_job_pr['quit'] = trained_le.transform(df_test_features_job_pr['quit'])
print(df_train_quit_pr['quit'].head())
print(df_test_features_job_pr['quit'].head())
id 910140 0 699916 1 417070 0 165489 0 613206 1 Name: quit, dtype: int32 id 485046 0 686555 0 467458 0 418655 0 789145 0 Name: quit, dtype: int32
RANDOM_STATE = 42
# загружаем данные
X_train_2 = df_train_quit_pr.drop(columns=['quit', 'last_year_violations'], axis=1)
y_train_2 = df_train_quit_pr['quit']
X_test_2 = df_test_features_job_pr.drop(columns=['quit', 'last_year_violations'], axis=1)
y_test_2 = df_test_features_job_pr['quit']
X_train_2.shape, X_test_2.shape
((1934, 8), (2000, 8))
# создаём списки с названиями признаков
ohe_columns_2 = ['dept', 'last_year_promo']
ord_columns_2 = ['level', 'workload']
num_columns_2 = ['employment_years','supervisor_evaluation','salary', 'job_satisfaction_rate']
# создаём пайплайн для подготовки признаков из списка ohe_columns: заполнение пропусков и OHE-кодирование
# SimpleImputer + OHE
ohe_pipe_2 = Pipeline(
[
(
'simpleImputer_ohe',
SimpleImputer(missing_values=np.nan, strategy='most_frequent')
),
(
'ohe',
OneHotEncoder(drop='first', handle_unknown='ignore', sparse_output=False)
)
]
)
# cоздаём пайплайн для подготовки признаков из списка ord_columns: заполнение пропусков и Ordinal-кодирование
# SimpleImputer + OE
ord_pipe_2 = Pipeline(
[('simpleImputer_before_ord', SimpleImputer(missing_values=np.nan, strategy='most_frequent')),
('ord', OrdinalEncoder(
categories=[
['junior', 'middle', 'sinior'],
['medium', 'low', 'high']
],
handle_unknown='use_encoded_value', unknown_value=np.nan
)
),
('simpleImputer_after_ord', SimpleImputer(missing_values=np.nan, strategy='most_frequent'))
]
)
# создаём общий пайплайн для подготовки данных
data_preprocessor_2 = ColumnTransformer(
[
('ohe', ohe_pipe_2, ohe_columns_2),
('ord', ord_pipe_2, ord_columns_2),
('num', MinMaxScaler(), num_columns_2)
],
remainder='passthrough'
)
# создаём итоговый пайплайн: подготовка данных и модель
pipe_final_2 = Pipeline(
[
('preprocessor', data_preprocessor_2),
('models', DecisionTreeClassifier(random_state=RANDOM_STATE))
]
)
param_grid_2 = [
{
'models': [KNeighborsClassifier()],
'models__n_neighbors': range(2, 5),
'preprocessor__num': [StandardScaler(), MinMaxScaler(), RobustScaler(),'passthrough']
},
{
'models': [DecisionTreeClassifier(random_state=RANDOM_STATE)],
'models__max_depth': range(2, 4),
'preprocessor__num': [StandardScaler(), MinMaxScaler(), RobustScaler(),'passthrough']
},
# словарь для модели SVC()
{
'models': [SVC(probability=True,random_state=RANDOM_STATE)],
'preprocessor__num': [StandardScaler(), MinMaxScaler(), RobustScaler(),'passthrough']
},
# словарь для модели LogisticRegression()
{
'models': [LogisticRegression(penalty='elasticnet' ,class_weight='balanced',solver='saga', random_state=RANDOM_STATE)],
'models__C': range(1,3),
'preprocessor__num': [StandardScaler(), MinMaxScaler(), RobustScaler(),'passthrough']
}
]
grid_2 = GridSearchCV(
pipe_final_2,
param_grid=param_grid_2,
cv=5,
# задайте метрику ROC-AUC
scoring='roc_auc',
n_jobs=-1
)
grid_2.fit(X_train_2, y_train_2)
print('Лучшая модель и её параметры:\n\n', grid_2.best_estimator_)
Лучшая модель и её параметры:
Pipeline(steps=[('preprocessor',
ColumnTransformer(remainder='passthrough',
transformers=[('ohe',
Pipeline(steps=[('simpleImputer_ohe',
SimpleImputer(strategy='most_frequent')),
('ohe',
OneHotEncoder(drop='first',
handle_unknown='ignore',
sparse_output=False))]),
['dept', 'last_year_promo']),
('ord',
Pipeline(steps=[('simpleImputer_before_ord',
SimpleImputer(st...
OrdinalEncoder(categories=[['junior',
'middle',
'sinior'],
['medium',
'low',
'high']],
handle_unknown='use_encoded_value',
unknown_value=nan)),
('simpleImputer_after_ord',
SimpleImputer(strategy='most_frequent'))]),
['level', 'workload']),
('num', StandardScaler(),
['employment_years',
'supervisor_evaluation',
'salary',
'job_satisfaction_rate'])])),
('models', SVC(probability=True, random_state=42))])
# Выведем результаты в виде датафрейм
pd.DataFrame(grid_2.cv_results_)[
['std_test_score', 'rank_test_score', 'param_models', 'mean_test_score','params']
].sort_values('rank_test_score').head(10)
| std_test_score | rank_test_score | param_models | mean_test_score | params | |
|---|---|---|---|---|---|
| 20 | 0.02 | 1 | SVC(probability=True, random_state=42) | 0.89 | {'models': SVC(probability=True, random_state=42), 'preprocessor__num': StandardScaler()} |
| 22 | 0.02 | 2 | SVC(probability=True, random_state=42) | 0.89 | {'models': SVC(probability=True, random_state=42), 'preprocessor__num': RobustScaler()} |
| 21 | 0.02 | 3 | SVC(probability=True, random_state=42) | 0.88 | {'models': SVC(probability=True, random_state=42), 'preprocessor__num': MinMaxScaler()} |
| 18 | 0.01 | 4 | DecisionTreeClassifier(random_state=42) | 0.87 | {'models': DecisionTreeClassifier(random_state=42), 'models__max_depth': 3, 'preprocessor__num': RobustScaler()} |
| 16 | 0.01 | 4 | DecisionTreeClassifier(random_state=42) | 0.87 | {'models': DecisionTreeClassifier(random_state=42), 'models__max_depth': 3, 'preprocessor__num': StandardScaler()} |
| 19 | 0.01 | 4 | DecisionTreeClassifier(random_state=42) | 0.87 | {'models': DecisionTreeClassifier(random_state=42), 'models__max_depth': 3, 'preprocessor__num': 'passthrough'} |
| 17 | 0.01 | 4 | DecisionTreeClassifier(random_state=42) | 0.87 | {'models': DecisionTreeClassifier(random_state=42), 'models__max_depth': 3, 'preprocessor__num': MinMaxScaler()} |
| 9 | 0.02 | 8 | KNeighborsClassifier() | 0.86 | {'models': KNeighborsClassifier(), 'models__n_neighbors': 4, 'preprocessor__num': MinMaxScaler()} |
| 8 | 0.02 | 9 | KNeighborsClassifier() | 0.86 | {'models': KNeighborsClassifier(), 'models__n_neighbors': 4, 'preprocessor__num': StandardScaler()} |
| 10 | 0.02 | 10 | KNeighborsClassifier() | 0.86 | {'models': KNeighborsClassifier(), 'models__n_neighbors': 4, 'preprocessor__num': RobustScaler()} |
print('Лучшая модель и её параметры:\n\n', grid_2.best_estimator_)
print(line_1)
print('Параметры лучшей модели:', grid_2.best_params_)
print(line_1)
print('Метрика лучшей модели по кросс-валидации на обучающих данных:', grid_2.best_score_)
print(line_1)
Лучшая модель и её параметры:
Pipeline(steps=[('preprocessor',
ColumnTransformer(remainder='passthrough',
transformers=[('ohe',
Pipeline(steps=[('simpleImputer_ohe',
SimpleImputer(strategy='most_frequent')),
('ohe',
OneHotEncoder(drop='first',
handle_unknown='ignore',
sparse_output=False))]),
['dept', 'last_year_promo']),
('ord',
Pipeline(steps=[('simpleImputer_before_ord',
SimpleImputer(st...
OrdinalEncoder(categories=[['junior',
'middle',
'sinior'],
['medium',
'low',
'high']],
handle_unknown='use_encoded_value',
unknown_value=nan)),
('simpleImputer_after_ord',
SimpleImputer(strategy='most_frequent'))]),
['level', 'workload']),
('num', StandardScaler(),
['employment_years',
'supervisor_evaluation',
'salary',
'job_satisfaction_rate'])])),
('models', SVC(probability=True, random_state=42))])
-----------------------------------------------------------------------------------------------------------------------------
Параметры лучшей модели: {'models': SVC(probability=True, random_state=42), 'preprocessor__num': StandardScaler()}
-----------------------------------------------------------------------------------------------------------------------------
Метрика лучшей модели по кросс-валидации на обучающих данных: 0.8885296597584732
-----------------------------------------------------------------------------------------------------------------------------
#проверьте работу модели на тестовой выборке
# рассчитайте прогноз на тестовых данных
y_test_pred_2 = grid_2.predict(X_test_2)
print(f'Метрика ROC-AUC на тестовой выборке: {roc_auc_score(y_test_2, y_test_pred_2)}')
Метрика ROC-AUC на тестовой выборке: 0.8708291353049251
y_test_pred_2 = grid_2.predict(X_test_2)
print(metrics.classification_report(y_test_2, y_test_pred_2))
ac_lr=metrics.accuracy_score(y_test_2, y_test_pred_2)
print(f"Model aniqligi:, ac_lr={metrics.accuracy_score(y_test_2, y_test_pred_2)}")
## confusion matrix
conf_mat = metrics.confusion_matrix(y_test_2, y_test_pred_2)
sns.heatmap(conf_mat, annot=True,fmt="g")
plt.show()
## ROC curve
fpr, tpr, thresholds = metrics.roc_curve(y_test_2, y_test_pred_2)
roc_auc = metrics.auc(fpr, tpr)
display = metrics.RocCurveDisplay(fpr=fpr, tpr=tpr, roc_auc=roc_auc, estimator_name='ROC curve')
display.plot()
plt.show()
precision recall f1-score support
0 0.93 0.93 0.93 1436
1 0.83 0.81 0.82 564
accuracy 0.90 2000
macro avg 0.88 0.87 0.87 2000
weighted avg 0.90 0.90 0.90 2000
Model aniqligi:, ac_lr=0.898
Приведенные графики показывают, что наша модель лучше предсказывает неуволившихся сотрудников (класс 0). Это можно увидеть по метрикам, которые распечатаны перед графиками: все метрики класса 0 выше чем метрики класса 1. Количество TN составляет 1340 из 2000 чел. Но посмотрим на распределение ошибок: Количество FN(ошибочнооставшихся) больше чем FP(ошибочноуволившихся). Вспомним условие задачи: нам необходимо уменьшить финансовые потери бизнеса всвязи с увольнением сотрудников, т.е в нашей ситуации считаю необходимым уменьшить FN, т.е модель должна точнее предсказывать сотрудников, которые хотят увольняться. Accuracy - это метрика, которая характеризует качество модели, агрегированное по всем классам. в случае ассиметрии классов (похоже на наш случай), можно использовать метрики, которые не учитывают TN и ориентируются на TP. Если мы рассмотрим долю правильно предсказанных положительных объектов среди всех объектов, предсказанных положительным классом, то мы получим метрику, которая называется точностью (precision). Чем меньше ложноположительных срабатываний будет допускать модель, тем больше будет её Precision. Если же мы рассмотрим долю правильно найденных положительных объектов среди всех объектов положительного класса, то мы получим метрику, которая называется полнотой (recall). модели очень удобно сравнивать, когда их качество выражено одним числом. В случае пары Precision-Recall существует популярный способ скомпоновать их в одну метрику - взять их среднее гармоническое. Данный показатель эффективности исторически носит название F1-меры. мы хотим учитывать ошибки на объектах обоих классов. При уменьшении порога отсечения мы будем находить (правильно предсказывать) всё большее число положительных объектов, но также и неправильно предсказывать положительную метку на всё большем числе отрицательных объектов. Естественным кажется ввести две метрики TPR и FPR: Обе эти величины растут при уменьшении порога. Кривая в осях TPR/FPR, которая получается при варьировании порога, исторически называется ROC-кривой (receiver operating characteristics curve, сокращённо ROC curve).Сейчас наша модель имеет метрику 0,86. Улучшить метрику можно изменяя пороговые значения: т.е изменяя порог принятия решения. Разные пороговые значения принятия решения создают разные TPR и FPR. Они представляют те самые точки, что образуют кривую ROC.Хорошая метрика модели машинного обучения должна отображать истинную и постоянную способность модели к прогнозированию. Это означает, что, если мы изменим тестовый набор данных, он не должен давать другой результат.ROC-кривая учитывает не только результаты классификации, но и вероятность предсказания всех классов. рассчитаем Площадь ROC-кривой для нашей логистической модели (используется метод predict_proba) и получим метрику AUC-ROC
probabilities = grid_2.predict_proba(X_test_2)
probabilities_one = probabilities[:, 1]
print('Площадь ROC-кривой:', roc_auc_score(y_test_2, probabilities_one))
Площадь ROC-кривой: 0.9254053566842491
Таким образом площадь ROC-кривой (AUC-ROC) для нашей модели равна 0.93, что свидетельствует о хорошем качестве модели. Проверим качество нашей модели с помощью DummyClassifier
def judge_model(model, name, plot=False):
print(name)
print('-'*20)
print('Training Performance')
print('-> Acc:', accuracy_score(y_train_2, model.predict(X_train_2)) )
print('-> AUC:', roc_auc_score(y_train_2, model.predict_proba(X_train_2)[:, 1] ))
print('Testing Performance')
print('-> Acc:', accuracy_score(y_train_2, model.predict(X_train_2)) )
print('-> AUC:', roc_auc_score(y_test_2, model.predict_proba(X_test_2)[:, 1] ))
print()
if plot:
fpr, tpr, thres = roc_curve(y_test_2, model.predict_proba(X_test_2)[:, 1])
graph.figure(figsize=(4, 4))
graph.plot(fpr, tpr, label='Test')
graph.xlabel('FPR')
graph.ylabel('TPR')
graph.show()
for strategy in ['stratified', 'most_frequent', 'prior', 'uniform']:
dummy = DummyClassifier(strategy=strategy)
dummy.fit(X_train_2, y_train_2)
judge_model(dummy, 'Dummy {}'.format(strategy), plot=True)
Dummy stratified -------------------- Training Performance -> Acc: 0.6447776628748707 -> AUC: 0.5133747928879775 Testing Performance -> Acc: 0.655635987590486 -> AUC: 0.51152235326656
Dummy most_frequent -------------------- Training Performance -> Acc: 0.7647362978283351 -> AUC: 0.5 Testing Performance -> Acc: 0.7647362978283351 -> AUC: 0.5
Dummy prior -------------------- Training Performance -> Acc: 0.7647362978283351 -> AUC: 0.5 Testing Performance -> Acc: 0.7647362978283351 -> AUC: 0.5
Dummy uniform -------------------- Training Performance -> Acc: 0.4937952430196484 -> AUC: 0.5 Testing Performance -> Acc: 0.49120992761116855 -> AUC: 0.5
judge_model(grid_2.best_estimator_, 'Лучшая модель и ее метрики: ', plot=True)
Лучшая модель и ее метрики: -------------------- Training Performance -> Acc: 0.8872802481902792 -> AUC: 0.9102044000624121 Testing Performance -> Acc: 0.8872802481902792 -> AUC: 0.9254053566842491
Проведена проверка работоспособности модели с помощью DummyClassifier. Использованы разные стратегии, Метрика AUC-ROC составила в среднем 0,5, что ниже метрики нашей модели на тестовой выборке, а значит мы действительно получили хорошую модель.
Посмотрим как распределены признаки в нашей модели:
shap.initjs()
X_test_transformed_2 = grid_2.best_estimator_.named_steps['preprocessor'].transform(X_test_2)
model_2 = grid_2.best_estimator_.named_steps['models'].predict
X_train_transformed_2 = grid_2.best_estimator_.named_steps['preprocessor'].transform(X_train_2)
explainer_2 = shap.Explainer(model_2, X_train_transformed_2)
shap_values_2 = explainer_2.shap_values(X_test_transformed_2)
feature_names_2 =list(map(lambda x: x.split('__')[1][:20],grid_2.best_estimator_.named_steps['preprocessor'].get_feature_names_out()))
plt.ylabel("Наименование признаков", fontsize=16)
plt.title("Feature Importance - Bar", fontsize=16)
shap.summary_plot(shap_values_2, X_test_transformed_2, feature_names=feature_names_2, plot_type="bar")
plt.ylabel("Наименование признаков", fontsize=16)
plt.title("Feature Importance - Dot", fontsize=16)
shap.summary_plot(shap_values_2, X_test_transformed_2, feature_names=feature_names_2, plot_type="dot");
# Adjust layout and display the plots side by side
plt.tight_layout()
plt.show()
PermutationExplainer explainer: 2001it [21:48, 1.52it/s]
<Figure size 2000x700 with 0 Axes>
График shap показывает, Что самое большое влияние на целевой признак оказывет признак стаж работы (чем меньше стаж, тем вероятнее сотрудник уволится). Также на решение об увольнении влияет уровень зарплаты (при уменьшении уровня зарплаты возрастает вероятность увольнения), уровень занимаемой должности (чем он ниже, тем вероятнее увольнение сотрудника), и нагрузка на работе( при увеличении нагрузки сотрудник может принять решение об увольнении). Удовлетворение от работы стоит на четвертом месте по влиянию на целевой признак, чем ниже удовлетворенность, тем больше вероятность того, что сотрудник уволится. Различные поощрения и отдел оказывают минимальное влияние на вероятность увольнения.
Модель классификации позволила нам с достаточно большой точностью составить портрет сотрудника, который может принять решение об увольнении: это сотрудник из технического отдела уровня junior, недавноработающий, получающий небольшую зарплату, но сильнозагруженный.
Перед нами была поставлена задача: разработать решение, которое позволит избежать финансовых потерь и оттока сотрудников.
Исходные данные: три датафрейма, которые были выгружены, произведена предобработка данных, позволившая снизить вес датафреймов в среднем на 30%. устранены дубликаты, изменен тип данных столбцов. Проведен исследовательский анализ данных: построены графики. Разработан pipline, для подготовки к обучению модели. Для решения задачи №1 (предсказания удовлетворенностью работой) лучшая модель для решения задачи регрессии это DecisionTreeRegressor(random_state=42), метрика SMAPE =14,2. Именно данная модель позволила нам сделать прогноз и ввести прогнозные значения в качестве нового признака для задачи№2. После выгрузки новых данных и добавления нового признака, данные были предобработаны, и подготовлены в pipline. Лучшей моделью классификации была выбрана модель SVC(probability=True, random_state=42)